diff --git a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java index e553c87ceec2..291f7420d554 100644 --- a/src/jmh/java/org/jabref/benchmarks/Benchmarks.java +++ b/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -1,10 +1,8 @@ package org.jabref.benchmarks; -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.List; -import java.util.Random; +import static org.mockito.Mockito.mock; + +import com.airhacks.afterburner.injection.Injector; import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; @@ -31,8 +29,6 @@ import org.jabref.model.groups.KeywordGroup; import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; - -import com.airhacks.afterburner.injection.Injector; import org.openjdk.jmh.Main; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; @@ -40,7 +36,11 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.runner.RunnerException; -import static org.mockito.Mockito.mock; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.List; +import java.util.Random; @State(Scope.Thread) public class Benchmarks { @@ -59,7 +59,9 @@ public void init() throws Exception { BibEntry entry = new BibEntry(); entry.setCitationKey("id" + i); entry.setField(StandardField.TITLE, "This is my title " + i); - entry.setField(StandardField.AUTHOR, "Firstname Lastname and FirstnameA LastnameA and FirstnameB LastnameB" + i); + entry.setField( + StandardField.AUTHOR, + "Firstname Lastname and FirstnameA LastnameA and FirstnameB LastnameB" + i); entry.setField(StandardField.JOURNAL, "Journal Title " + i); entry.setField(StandardField.KEYWORDS, "testkeyword"); entry.setField(StandardField.YEAR, "1" + i); @@ -69,21 +71,25 @@ public void init() throws Exception { bibtexString = getOutputWriter().toString(); - latexConversionString = "{A} \\textbf{bold} approach {\\it to} ${{\\Sigma}}{\\Delta}$ modulator \\textsuperscript{2} \\$"; + latexConversionString = + "{A} \\textbf{bold} approach {\\it to} ${{\\Sigma}}{\\Delta}$ modulator \\textsuperscript{2} \\$"; - htmlConversionString = "Österreich – & characters ⪢ italic"; + htmlConversionString = + "Österreich – & characters ⪢ italic"; } private StringWriter getOutputWriter() throws IOException { StringWriter outputWriter = new StringWriter(); BibWriter bibWriter = new BibWriter(outputWriter, OS.NEWLINE); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter( - bibWriter, - mock(SelfContainedSaveConfiguration.class), - mock(FieldPreferences.class), - mock(CitationKeyPatternPreferences.class), - new BibEntryTypesManager()); - databaseWriter.savePartOfDatabase(new BibDatabaseContext(database, new MetaData()), database.getEntries()); + BibtexDatabaseWriter databaseWriter = + new BibtexDatabaseWriter( + bibWriter, + mock(SelfContainedSaveConfiguration.class), + mock(FieldPreferences.class), + mock(CitationKeyPatternPreferences.class), + new BibEntryTypesManager()); + databaseWriter.savePartOfDatabase( + new BibDatabaseContext(database, new MetaData()), database.getEntries()); return outputWriter; } @@ -136,7 +142,15 @@ public String htmlToLatexConversion() { @Benchmark public boolean keywordGroupContains() { - KeywordGroup group = new WordKeywordGroup("testGroup", GroupHierarchyType.INDEPENDENT, StandardField.KEYWORDS, "testkeyword", false, ',', false); + KeywordGroup group = + new WordKeywordGroup( + "testGroup", + GroupHierarchyType.INDEPENDENT, + StandardField.KEYWORDS, + "testkeyword", + false, + ',', + false); return group.containsAll(database.getEntries()); } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 852af1ba3961..6cd6e00dc8d6 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -12,35 +12,34 @@ requires javafx.controls; requires javafx.web; requires javafx.fxml; - requires afterburner.fx; - provides com.airhacks.afterburner.views.ResourceLocator - with org.jabref.gui.util.JabRefResourceLocator; + + provides com.airhacks.afterburner.views.ResourceLocator with + org.jabref.gui.util.JabRefResourceLocator; requires com.dlsc.gemsfx; + uses com.dlsc.gemsfx.TagsField; + // Provides number input fields for parameters in AI expert settings requires com.dlsc.unitfx; - requires com.tobiasdiez.easybind; - requires de.saxsys.mvvmfx; requires de.saxsys.mvvmfx.validation; - requires org.controlsfx.controls; requires org.fxmisc.flowless; requires org.fxmisc.richtext; - requires org.kordamp.ikonli.core; requires org.kordamp.ikonli.javafx; requires org.kordamp.ikonli.materialdesign2; + uses org.kordamp.ikonli.IkonHandler; uses org.kordamp.ikonli.IkonProvider; - provides org.kordamp.ikonli.IkonHandler - with org.jabref.gui.icon.JabRefIkonHandler; - provides org.kordamp.ikonli.IkonProvider - with org.jabref.gui.icon.JabrefIconProvider; + provides org.kordamp.ikonli.IkonHandler with + org.jabref.gui.icon.JabRefIkonHandler; + provides org.kordamp.ikonli.IkonProvider with + org.jabref.gui.icon.JabrefIconProvider; requires reactfx; // endregion @@ -52,10 +51,11 @@ requires org.tinylog.api; requires org.tinylog.api.slf4j; requires org.tinylog.impl; + // endregion - provides org.tinylog.writers.Writer - with org.jabref.gui.logging.GuiWriter; + provides org.tinylog.writers.Writer with + org.jabref.gui.logging.GuiWriter; // Preferences and XML requires java.prefs; @@ -100,7 +100,9 @@ requires ojdbc10; requires org.postgresql.jdbc; requires org.mariadb.jdbc; + uses org.mariadb.jdbc.credential.CredentialPlugin; + // endregion // region: Apache Commons and other (similar) helper libraries @@ -123,23 +125,17 @@ requires jbibtex; requires citeproc.java; - requires snuggletex.core; - requires org.apache.pdfbox; requires org.apache.xmpbox; requires com.ibm.icu; - requires flexmark; requires flexmark.html2md.converter; requires flexmark.util.ast; requires flexmark.util.data; - requires com.h2database.mvstore; - requires java.keyring; requires org.freedesktop.dbus; - requires org.jooq.jool; // region AI @@ -153,6 +149,7 @@ requires langchain4j.hugging.face; requires langchain4j.mistral.ai; requires langchain4j.open.ai; + uses ai.djl.engine.EngineProvider; uses ai.djl.repository.RepositoryFactory; uses ai.djl.repository.zoo.ZooProvider; @@ -164,6 +161,7 @@ * In case the version is updated, please also increment {@link org.jabref.model.search.SearchFieldConstants#VERSION} to trigger reindexing. */ uses org.apache.lucene.codecs.lucene99.Lucene99Codec; + requires org.apache.lucene.analysis.common; requires org.apache.lucene.core; requires org.apache.lucene.highlighter; @@ -173,8 +171,8 @@ requires net.harawata.appdirs; requires com.sun.jna; requires com.sun.jna.platform; - requires org.eclipse.jgit; + uses org.eclipse.jgit.transport.SshSessionFactory; uses org.eclipse.jgit.lib.Signer; @@ -189,5 +187,5 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; - // endregion +// endregion } diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java index 947c2f3dd343..493f4c8758e2 100644 --- a/src/main/java/org/jabref/Launcher.java +++ b/src/main/java/org/jabref/Launcher.java @@ -1,16 +1,8 @@ package org.jabref; -import java.io.File; -import java.io.IOException; -import java.net.Authenticator; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import com.airhacks.afterburner.injection.Injector; +import org.apache.commons.cli.ParseException; import org.jabref.cli.ArgumentProcessor; import org.jabref.cli.JabRefCLI; import org.jabref.gui.JabRefGUI; @@ -37,14 +29,22 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.injection.Injector; -import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import org.tinylog.configuration.Configuration; +import java.io.File; +import java.io.IOException; +import java.net.Authenticator; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + /** * The main entry point for the JabRef application. *

@@ -76,8 +76,13 @@ public static void main(String[] args) { PreferencesMigrations.runMigrations(preferences, 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())); configureProxy(preferences.getProxyPreferences()); configureSSL(preferences.getSSLPreferences()); @@ -87,19 +92,21 @@ public static void main(String[] args) { try { DefaultFileUpdateMonitor fileUpdateMonitor = new DefaultFileUpdateMonitor(); Injector.setModelOrService(FileUpdateMonitor.class, fileUpdateMonitor); - HeadlessExecutorService.INSTANCE.executeInterruptableTask(fileUpdateMonitor, "FileUpdateMonitor"); + HeadlessExecutorService.INSTANCE.executeInterruptableTask( + fileUpdateMonitor, "FileUpdateMonitor"); DirectoryMonitor directoryMonitor = new DefaultDirectoryMonitor(); Injector.setModelOrService(DirectoryMonitor.class, directoryMonitor); // Process arguments - ArgumentProcessor argumentProcessor = new ArgumentProcessor( - args, - ArgumentProcessor.Mode.INITIAL_START, - preferences, - preferences, - fileUpdateMonitor, - entryTypesManager); + ArgumentProcessor argumentProcessor = + new ArgumentProcessor( + args, + ArgumentProcessor.Mode.INITIAL_START, + preferences, + preferences, + fileUpdateMonitor, + entryTypesManager); argumentProcessor.processArguments(); if (argumentProcessor.shouldShutDown()) { LOGGER.debug("JabRef shut down after processing command line arguments"); @@ -151,15 +158,18 @@ private 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", isDebugEnabled ? "debug" : "info", - "writerFile", "rolling file", - "writerFile.level", isDebugEnabled ? "debug" : "info", - // 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"); + Map configuration = + Map.of( + "level", isDebugEnabled ? "debug" : "info", + "writerFile", "rolling file", + "writerFile.level", isDebugEnabled ? "debug" : "info", + // 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"); configuration.forEach(Configuration::set); LOGGER = LoggerFactory.getLogger(Launcher.class); @@ -168,7 +178,8 @@ private static void initLogging(String[] args) { /** * @return true if JabRef should continue starting up, false if it should quit. */ - private static boolean handleMultipleAppInstances(String[] args, RemotePreferences remotePreferences) throws InterruptedException { + private static boolean handleMultipleAppInstances( + String[] args, RemotePreferences remotePreferences) throws InterruptedException { LOGGER.trace("Checking for remote handling..."); if (remotePreferences.useRemoteServer()) { // Try to contact already running JabRef @@ -176,15 +187,20 @@ private static boolean handleMultipleAppInstances(String[] args, RemotePreferenc 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. - LOGGER.warn("This JabRef instance is already running. Please switch to that instance."); + // There is already a server out there, avoid showing log "Passing arguments" + // while no arguments are provided. + LOGGER.warn( + "This JabRef instance is already running. Please switch to that instance."); } else { - // We are not alone, there is already a server out there, send command line arguments to other instance + // 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. - LOGGER.info("Arguments passed on to running JabRef instance. Shutting down."); + // 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."); } else { LOGGER.warn("Could not communicate with other running JabRef instance."); } @@ -206,7 +222,8 @@ private static void configureProxy(ProxyPreferences proxyPreferences) { } private static void configureSSL(SSLPreferences sslPreferences) { - TrustStoreManager.createTruststoreFileIfNotExist(Path.of(sslPreferences.getTruststorePath())); + TrustStoreManager.createTruststoreFileIfNotExist( + Path.of(sslPreferences.getTruststorePath())); } private static void clearOldSearchIndices() { @@ -221,7 +238,9 @@ private static void clearOldSearchIndices() { try (DirectoryStream stream = Files.newDirectoryStream(appData)) { for (Path path : stream) { - if (Files.isDirectory(path) && !path.toString().endsWith("ssl") && path.toString().contains("lucene") + if (Files.isDirectory(path) + && !path.toString().endsWith("ssl") + && path.toString().contains("lucene") && !path.equals(currentIndexPath)) { LOGGER.info("Deleting out-of-date fulltext search index at {}.", path); Files.walk(path) diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index 64edddac7a4f..26624075e34c 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -1,17 +1,7 @@ package org.jabref.cli; -import java.io.IOException; -import java.net.MalformedURLException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; -import java.util.prefs.BackingStoreException; +import com.airhacks.afterburner.injection.Injector; +import com.google.common.base.Throwables; import org.jabref.gui.externalfiles.AutoSetFileLinksUtil; import org.jabref.gui.preferences.GuiPreferences; @@ -59,16 +49,29 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.injection.Injector; -import com.google.common.base.Throwables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.prefs.BackingStoreException; + 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 JabRefCLI cli; @@ -88,12 +91,13 @@ public enum Mode { INITIAL_START, REMOTE_START } * * @implNote both cli and gui preferences are passed to make the dependency to GUI parts explicit */ - public ArgumentProcessor(String[] args, - Mode startupMode, - CliPreferences cliPreferences, - GuiPreferences guiPreferences, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager) + public ArgumentProcessor( + String[] args, + Mode startupMode, + CliPreferences cliPreferences, + GuiPreferences guiPreferences, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) throws org.apache.commons.cli.ParseException { this.cli = new JabRefCLI(args); this.startupMode = startupMode; @@ -114,7 +118,8 @@ private Optional importToOpenBase(String importArguments) { return result; } - private Optional importBibtexToOpenBase(String argument, ImportFormatPreferences importFormatPreferences) { + private Optional importBibtexToOpenBase( + String argument, ImportFormatPreferences importFormatPreferences) { BibtexParser parser = new BibtexParser(importFormatPreferences); try { List entries = parser.parseEntries(argument); @@ -122,7 +127,10 @@ private Optional importBibtexToOpenBase(String argument, ImportFor result.setToOpenTab(); return Optional.of(result); } catch (ParseException e) { - System.err.println(Localization.lang("Error occurred when parsing entry") + ": " + e.getLocalizedMessage()); + System.err.println( + Localization.lang("Error occurred when parsing entry") + + ": " + + e.getLocalizedMessage()); return Optional.empty(); } } @@ -137,12 +145,16 @@ private Optional importFile(String importArguments) { String address = data[0]; Path file; - if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { + if (address.startsWith("http://") + || address.startsWith("https://") + || address.startsWith("ftp://")) { // Download web resource to temporary file try { file = new URLDownload(address).toTemporaryFile(); } catch (FetcherException | MalformedURLException e) { - System.err.println(Localization.lang("Problem downloading from %1", address) + e.getLocalizedMessage()); + System.err.println( + Localization.lang("Problem downloading from %1", address) + + e.getLocalizedMessage()); return Optional.empty(); } } else { @@ -161,22 +173,23 @@ private Optional importFile(String importArguments) { } Optional importResult = importFile(file, importFormat); - importResult.ifPresent(result -> { - if (result.hasWarnings()) { - System.out.println(result.getErrorMessage()); - } - }); + importResult.ifPresent( + result -> { + if (result.hasWarnings()) { + System.out.println(result.getErrorMessage()); + } + }); return importResult; } private Optional importFile(Path file, String importFormat) { try { - ImportFormatReader importFormatReader = new ImportFormatReader( - cliPreferences.getImporterPreferences(), - cliPreferences.getImportFormatPreferences(), - cliPreferences.getCitationKeyPatternPreferences(), - fileUpdateMonitor - ); + ImportFormatReader importFormatReader = + new ImportFormatReader( + cliPreferences.getImporterPreferences(), + cliPreferences.getImportFormatPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), + fileUpdateMonitor); if (!"*".equals(importFormat)) { System.out.println(Localization.lang("Importing %0", file)); @@ -193,7 +206,10 @@ private Optional importFile(Path file, String importFormat) { return Optional.of(importResult.parserResult()); } } catch (ImportException ex) { - System.err.println(Localization.lang("Error opening file '%0'", file) + "\n" + ex.getLocalizedMessage()); + System.err.println( + Localization.lang("Error opening file '%0'", file) + + "\n" + + ex.getLocalizedMessage()); return Optional.empty(); } } @@ -235,7 +251,8 @@ public void processArguments() { return; } } else { - System.err.println(Localization.lang("The output option depends on a valid input option.")); + System.err.println( + Localization.lang("The output option depends on a valid input option.")); } } @@ -247,13 +264,17 @@ public void processArguments() { automaticallySetFileLinks(loaded); } - if ((cli.isWriteXmpToPdf() && cli.isEmbedBibFileInPdf()) || (cli.isWriteMetadataToPdf() && (cli.isWriteXmpToPdf() || cli.isEmbedBibFileInPdf()))) { - System.err.println("Give only one of [writeXmpToPdf, embedBibFileInPdf, writeMetadataToPdf]"); + if ((cli.isWriteXmpToPdf() && cli.isEmbedBibFileInPdf()) + || (cli.isWriteMetadataToPdf() + && (cli.isWriteXmpToPdf() || cli.isEmbedBibFileInPdf()))) { + System.err.println( + "Give only one of [writeXmpToPdf, embedBibFileInPdf, writeMetadataToPdf]"); } if (cli.isWriteMetadataToPdf() || cli.isWriteXmpToPdf() || cli.isEmbedBibFileInPdf()) { if (!loaded.isEmpty()) { - writeMetadataToPdf(loaded, + writeMetadataToPdf( + loaded, cli.getWriteMetadataToPdf(), cliPreferences.getXmpPreferences(), cliPreferences.getFilePreferences(), @@ -271,7 +292,8 @@ public void processArguments() { exportFile(loaded, cli.getFileExport().split(",")); LOGGER.debug("Finished export"); } else { - System.err.println(Localization.lang("The output option depends on a valid import option.")); + System.err.println( + Localization.lang("The output option depends on a valid import option.")); } } @@ -300,16 +322,17 @@ public void processArguments() { } } - private void writeMetadataToPdf(List loaded, - String filesAndCiteKeys, - XmpPreferences xmpPreferences, - FilePreferences filePreferences, - BibDatabaseMode databaseMode, - BibEntryTypesManager entryTypesManager, - FieldPreferences fieldPreferences, - JournalAbbreviationRepository abbreviationRepository, - boolean writeXMP, - boolean embeddBibfile) { + private void writeMetadataToPdf( + List loaded, + String filesAndCiteKeys, + XmpPreferences xmpPreferences, + FilePreferences filePreferences, + BibDatabaseMode databaseMode, + BibEntryTypesManager entryTypesManager, + FieldPreferences fieldPreferences, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { if (loaded.isEmpty()) { LOGGER.error("The write xmp option depends on a valid import option."); return; @@ -318,7 +341,8 @@ private void writeMetadataToPdf(List loaded, BibDatabaseContext databaseContext = pr.getDatabaseContext(); XmpPdfExporter xmpPdfExporter = new XmpPdfExporter(xmpPreferences); - EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldPreferences); + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = + new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldPreferences); if ("all".equals(filesAndCiteKeys)) { for (BibEntry entry : databaseContext.getEntries()) { @@ -366,28 +390,47 @@ private void writeMetadataToPdf(List loaded, embeddBibfile); } - private void writeMetadataToPDFsOfEntry(BibDatabaseContext databaseContext, - String citeKey, - BibEntry entry, - FilePreferences filePreferences, - XmpPdfExporter xmpPdfExporter, - EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, - JournalAbbreviationRepository abbreviationRepository, - boolean writeXMP, - boolean embedBibfile) { + private void writeMetadataToPDFsOfEntry( + BibDatabaseContext databaseContext, + String citeKey, + BibEntry entry, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embedBibfile) { try { if (writeXMP) { - if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { - System.out.printf("Successfully written XMP metadata on at least one linked file of %s%n", citeKey); + if (xmpPdfExporter.exportToAllFilesOfEntry( + databaseContext, + filePreferences, + entry, + List.of(entry), + abbreviationRepository)) { + System.out.printf( + "Successfully written XMP metadata on at least one linked file of %s%n", + citeKey); } else { - System.err.printf("Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); + System.err.printf( + "Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", + citeKey); } } if (embedBibfile) { - if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { - System.out.printf("Successfully embedded metadata on at least one linked file of %s%n", citeKey); + if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry( + databaseContext, + filePreferences, + entry, + List.of(entry), + abbreviationRepository)) { + System.out.printf( + "Successfully embedded metadata on at least one linked file of %s%n", + citeKey); } else { - System.out.printf("Cannot embed metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); + System.out.printf( + "Cannot embed metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", + citeKey); } } } catch (Exception e) { @@ -395,53 +438,85 @@ private void writeMetadataToPDFsOfEntry(BibDatabaseContext databaseContext, } } - private void writeMetadataToPdfByCitekey(BibDatabaseContext databaseContext, - List citeKeys, - FilePreferences filePreferences, - XmpPdfExporter xmpPdfExporter, - EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, - JournalAbbreviationRepository abbreviationRepository, - boolean writeXMP, - boolean embeddBibfile) { + private void writeMetadataToPdfByCitekey( + BibDatabaseContext databaseContext, + List citeKeys, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { for (String citeKey : citeKeys) { - List bibEntryList = databaseContext.getDatabase().getEntriesByCitationKey(citeKey); + List bibEntryList = + databaseContext.getDatabase().getEntriesByCitationKey(citeKey); if (bibEntryList.isEmpty()) { System.err.printf("Skipped - Cannot find %s in library.%n", citeKey); continue; } for (BibEntry entry : bibEntryList) { - writeMetadataToPDFsOfEntry(databaseContext, citeKey, entry, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, abbreviationRepository, writeXMP, embeddBibfile); + writeMetadataToPDFsOfEntry( + databaseContext, + citeKey, + entry, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); } } } - private void writeMetadataToPdfByFileNames(BibDatabaseContext databaseContext, - List pdfs, - FilePreferences filePreferences, - XmpPdfExporter xmpPdfExporter, - EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, - JournalAbbreviationRepository abbreviationRepository, - boolean writeXMP, - boolean embeddBibfile) { + private void writeMetadataToPdfByFileNames( + BibDatabaseContext databaseContext, + List pdfs, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { for (String fileName : pdfs) { Path filePath = Path.of(fileName); if (!filePath.isAbsolute()) { - filePath = FileUtil.find(fileName, databaseContext.getFileDirectories(filePreferences)).orElse(FileUtil.find(fileName, List.of(Path.of("").toAbsolutePath())).orElse(filePath)); + filePath = + FileUtil.find(fileName, databaseContext.getFileDirectories(filePreferences)) + .orElse( + FileUtil.find( + fileName, + List.of(Path.of("").toAbsolutePath())) + .orElse(filePath)); } if (Files.exists(filePath)) { try { if (writeXMP) { - if (xmpPdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { - System.out.printf("Successfully written XMP metadata of at least one entry to %s%n", fileName); + if (xmpPdfExporter.exportToFileByPath( + databaseContext, + filePreferences, + filePath, + abbreviationRepository)) { + System.out.printf( + "Successfully written XMP metadata of at least one entry to %s%n", + fileName); } else { - System.out.printf("File %s is not linked to any entry in database.%n", fileName); + System.out.printf( + "File %s is not linked to any entry in database.%n", fileName); } } if (embeddBibfile) { - if (embeddedBibFilePdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { - System.out.printf("Successfully embedded XMP metadata of at least one entry to %s%n", fileName); + if (embeddedBibFilePdfExporter.exportToFileByPath( + databaseContext, + filePreferences, + filePath, + abbreviationRepository)) { + System.out.printf( + "Successfully embedded XMP metadata of at least one entry to %s%n", + fileName); } else { - System.out.printf("File %s is not linked to any entry in database.%n", fileName); + System.out.printf( + "File %s is not linked to any entry in database.%n", fileName); } } } catch (IOException e) { @@ -468,7 +543,13 @@ private boolean exportMatches(List loaded) { List matches; try { // extract current thread task executor from luceneManager - matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), cliPreferences.getFilePreferences()).getMatches(); + matches = + new DatabaseSearcher( + query, + databaseContext, + new CurrentThreadTaskExecutor(), + cliPreferences.getFilePreferences()) + .getMatches(); } catch (IOException e) { LOGGER.error("Error occurred when searching", e); return false; @@ -485,8 +566,12 @@ private boolean exportMatches(List loaded) { // default exporter: bib file formatName = "bib"; default -> { - System.err.println(Localization.lang("Output file missing").concat(". \n \t ") - .concat(Localization.lang("Usage")).concat(": ") + JabRefCLI.getExportMatchesSyntax()); + System.err.println( + Localization.lang("Output file missing") + .concat(". \n \t ") + .concat(Localization.lang("Usage")) + .concat(": ") + + JabRefCLI.getExportMatchesSyntax()); guiNeeded = false; return false; } @@ -499,9 +584,10 @@ private boolean exportMatches(List loaded) { LOGGER.debug("Finished export"); } else { // export new database - ExporterFactory exporterFactory = ExporterFactory.create( - cliPreferences, - Injector.instantiateModelOrService(BibEntryTypesManager.class)); + ExporterFactory exporterFactory = + ExporterFactory.create( + cliPreferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); Optional exporter = exporterFactory.getExporterByName(formatName); if (exporter.isEmpty()) { System.err.println(Localization.lang("Unknown export format %0", formatName)); @@ -509,14 +595,19 @@ private boolean exportMatches(List loaded) { // We have an TemplateExporter instance: try { System.out.println(Localization.lang("Exporting %0", data[1])); - exporter.get().export( - databaseContext, - Path.of(data[1]), - matches, - Collections.emptyList(), - Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + exporter.get() + .export( + databaseContext, + Path.of(data[1]), + matches, + Collections.emptyList(), + Injector.instantiateModelOrService( + JournalAbbreviationRepository.class)); } catch (Exception ex) { - System.err.println(Localization.lang("Could not export file '%0' (reason: %1)", data[1], Throwables.getStackTraceAsString(ex))); + System.err.println( + Localization.lang( + "Could not export file '%0' (reason: %1)", + data[1], Throwables.getStackTraceAsString(ex))); } } } @@ -558,11 +649,14 @@ private List importAndOpenFiles() { ParserResult pr = new ParserResult(); if (bibExtension) { try { - pr = OpenDatabase.loadDatabase( - Path.of(aLeftOver), - cliPreferences.getImportFormatPreferences(), - fileUpdateMonitor); - // In contrast to org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed, we do not execute OpenDatabaseAction.performPostOpenActions(result, dialogService); + pr = + OpenDatabase.loadDatabase( + Path.of(aLeftOver), + cliPreferences.getImportFormatPreferences(), + fileUpdateMonitor); + // In contrast to org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed, we do + // not execute OpenDatabaseAction.performPostOpenActions(result, + // dialogService); } catch (IOException ex) { pr = ParserResult.fromError(ex); LOGGER.error("Error opening file '{}'", aLeftOver, ex); @@ -600,7 +694,9 @@ private List importAndOpenFiles() { } if (!cli.isBlank() && cli.isBibtexImport()) { - importBibtexToOpenBase(cli.getBibtexImport(), cliPreferences.getImportFormatPreferences()).ifPresent(loaded::add); + importBibtexToOpenBase( + cli.getBibtexImport(), cliPreferences.getImportFormatPreferences()) + .ifPresent(loaded::add); } return loaded; @@ -633,26 +729,38 @@ private boolean generateAux(List loaded, String[] data) { private void saveDatabase(BibDatabase newBase, String subName) { try { System.out.println(Localization.lang("Saving") + ": " + subName); - try (AtomicFileWriter fileWriter = new AtomicFileWriter(Path.of(subName), StandardCharsets.UTF_8)) { + try (AtomicFileWriter fileWriter = + new AtomicFileWriter(Path.of(subName), StandardCharsets.UTF_8)) { BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); - SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withReformatOnSave(cliPreferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); - BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - cliPreferences.getFieldPreferences(), - cliPreferences.getCitationKeyPatternPreferences(), - entryTypesManager); + SelfContainedSaveConfiguration saveConfiguration = + (SelfContainedSaveConfiguration) + new SelfContainedSaveConfiguration() + .withReformatOnSave( + cliPreferences + .getLibraryPreferences() + .shouldAlwaysReformatOnSave()); + BibDatabaseWriter databaseWriter = + new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + cliPreferences.getFieldPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), + entryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(newBase)); // Show just a warning message if encoding did not work for all characters: if (fileWriter.hasEncodingProblems()) { - System.err.println(Localization.lang("Warning") + ": " - + Localization.lang("UTF-8 could not be used to encode the following characters: %0", fileWriter.getEncodingProblems())); + System.err.println( + Localization.lang("Warning") + + ": " + + Localization.lang( + "UTF-8 could not be used to encode the following characters: %0", + fileWriter.getEncodingProblems())); } } } catch (IOException ex) { - System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); + System.err.println( + Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); } } @@ -666,7 +774,8 @@ private void exportFile(List loaded, String[] data) { saveDatabase(pr.getDatabase(), data[0]); } } else { - System.err.println(Localization.lang("The output option depends on a valid import option.")); + System.err.println( + Localization.lang("The output option depends on a valid import option.")); } } else if (data.length == 2) { // This signals that the latest import should be stored in the given @@ -676,26 +785,32 @@ private void exportFile(List loaded, String[] data) { Path path = parserResult.getPath().get().toAbsolutePath(); BibDatabaseContext databaseContext = parserResult.getDatabaseContext(); databaseContext.setDatabasePath(path); - List fileDirForDatabase = databaseContext - .getFileDirectories(cliPreferences.getFilePreferences()); + List fileDirForDatabase = + databaseContext.getFileDirectories(cliPreferences.getFilePreferences()); System.out.println(Localization.lang("Exporting %0", data[0])); - ExporterFactory exporterFactory = ExporterFactory.create( - cliPreferences, - Injector.instantiateModelOrService(BibEntryTypesManager.class)); + ExporterFactory exporterFactory = + ExporterFactory.create( + cliPreferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); Optional exporter = exporterFactory.getExporterByName(data[1]); if (exporter.isEmpty()) { System.err.println(Localization.lang("Unknown export format %0", data[1])); } else { // We have an exporter: try { - exporter.get().export( - parserResult.getDatabaseContext(), - Path.of(data[0]), - parserResult.getDatabaseContext().getDatabase().getEntries(), - fileDirForDatabase, - Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + exporter.get() + .export( + parserResult.getDatabaseContext(), + Path.of(data[0]), + parserResult.getDatabaseContext().getDatabase().getEntries(), + fileDirForDatabase, + Injector.instantiateModelOrService( + JournalAbbreviationRepository.class)); } catch (Exception ex) { - System.err.println(Localization.lang("Could not export file '%0' (reason: %1)", data[0], Throwables.getStackTraceAsString(ex))); + System.err.println( + Localization.lang( + "Could not export file '%0' (reason: %1)", + data[0], Throwables.getStackTraceAsString(ex))); } } } @@ -704,7 +819,8 @@ private void exportFile(List loaded, String[] data) { private void importPreferences() { try { cliPreferences.importPreferences(Path.of(cli.getPreferencesImport())); - Injector.setModelOrService(BibEntryTypesManager.class, cliPreferences.getCustomEntryTypesRepository()); + Injector.setModelOrService( + BibEntryTypesManager.class, cliPreferences.getCustomEntryTypesRepository()); } catch (JabRefException ex) { LOGGER.error("Cannot import preferences", ex); } @@ -725,7 +841,8 @@ private void resetPreferences(String value) { for (String key : keys) { try { cliPreferences.deleteKey(key.trim()); - System.out.println(Localization.lang("Resetting preference key '%0'", key.trim())); + System.out.println( + Localization.lang("Resetting preference key '%0'", key.trim())); } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); } @@ -736,18 +853,24 @@ private void resetPreferences(String value) { private void automaticallySetFileLinks(List loaded) { for (ParserResult parserResult : loaded) { BibDatabase database = parserResult.getDatabase(); - LOGGER.info("Automatically setting file links for {}", - parserResult.getDatabaseContext().getDatabasePath() - .map(Path::getFileName) - .map(Path::toString).orElse("UNKNOWN")); - - AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( - parserResult.getDatabaseContext(), - guiPreferences.getExternalApplicationsPreferences(), - cliPreferences.getFilePreferences(), - cliPreferences.getAutoLinkPreferences()); + LOGGER.info( + "Automatically setting file links for {}", + parserResult + .getDatabaseContext() + .getDatabasePath() + .map(Path::getFileName) + .map(Path::toString) + .orElse("UNKNOWN")); + + AutoSetFileLinksUtil util = + new AutoSetFileLinksUtil( + parserResult.getDatabaseContext(), + guiPreferences.getExternalApplicationsPreferences(), + cliPreferences.getFilePreferences(), + cliPreferences.getAutoLinkPreferences()); - util.linkAssociatedFiles(database.getEntries(), (linkedFile, bibEntry) -> bibEntry.addFile(linkedFile)); + util.linkAssociatedFiles( + database.getEntries(), (linkedFile, bibEntry) -> bibEntry.addFile(linkedFile)); } } @@ -757,9 +880,10 @@ private void regenerateCitationKeys(List loaded) { LOGGER.info(Localization.lang("Regenerating citation keys according to metadata")); - CitationKeyGenerator keyGenerator = new CitationKeyGenerator( - parserResult.getDatabaseContext(), - cliPreferences.getCitationKeyPatternPreferences()); + CitationKeyGenerator keyGenerator = + new CitationKeyGenerator( + parserResult.getDatabaseContext(), + cliPreferences.getCitationKeyPatternPreferences()); for (BibEntry entry : database.getEntries()) { keyGenerator.generateAndSetKey(entry); } @@ -774,7 +898,8 @@ private void regenerateCitationKeys(List loaded) { */ private Optional fetch(String fetchCommand) { if ((fetchCommand == null) || !fetchCommand.contains(":")) { - System.out.println(Localization.lang("Expected syntax for --fetch=':'")); + System.out.println( + Localization.lang("Expected syntax for --fetch=':'")); System.out.println(Localization.lang("The following fetchers are available:")); return Optional.empty(); } @@ -783,12 +908,14 @@ private Optional fetch(String fetchCommand) { String engine = split[0]; String query = split[1]; - Set fetchers = WebFetchers.getSearchBasedFetchers( - cliPreferences.getImportFormatPreferences(), - cliPreferences.getImporterPreferences()); - Optional selectedFetcher = fetchers.stream() - .filter(fetcher -> fetcher.getName().equalsIgnoreCase(engine)) - .findFirst(); + Set fetchers = + WebFetchers.getSearchBasedFetchers( + cliPreferences.getImportFormatPreferences(), + cliPreferences.getImporterPreferences()); + Optional selectedFetcher = + fetchers.stream() + .filter(fetcher -> fetcher.getName().equalsIgnoreCase(engine)) + .findFirst(); if (selectedFetcher.isEmpty()) { System.out.println(Localization.lang("Could not find fetcher '%0'", engine)); @@ -797,7 +924,8 @@ private Optional fetch(String fetchCommand) { return Optional.empty(); } else { - System.out.println(Localization.lang("Running query '%0' with fetcher '%1'.", query, engine)); + System.out.println( + Localization.lang("Running query '%0' with fetcher '%1'.", query, engine)); System.out.print(Localization.lang("Please wait...")); try { List matches = selectedFetcher.get().performSearch(query); @@ -805,7 +933,10 @@ private Optional fetch(String fetchCommand) { System.out.println("\r" + Localization.lang("No results found.")); return Optional.empty(); } else { - System.out.println("\r" + Localization.lang("Found %0 results.", String.valueOf(matches.size()))); + System.out.println( + "\r" + + Localization.lang( + "Found %0 results.", String.valueOf(matches.size()))); return Optional.of(new ParserResult(matches)); } } catch (FetcherException e) { diff --git a/src/main/java/org/jabref/cli/AuxCommandLine.java b/src/main/java/org/jabref/cli/AuxCommandLine.java index 61f4c133008d..fe8d9c02e9ee 100644 --- a/src/main/java/org/jabref/cli/AuxCommandLine.java +++ b/src/main/java/org/jabref/cli/AuxCommandLine.java @@ -1,7 +1,5 @@ package org.jabref.cli; -import java.nio.file.Path; - import org.jabref.gui.auximport.AuxParserResultViewModel; import org.jabref.logic.auxparser.AuxParser; import org.jabref.logic.auxparser.AuxParserResult; @@ -9,6 +7,8 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.strings.StringUtil; +import java.nio.file.Path; + public class AuxCommandLine { private final String auxFile; private final BibDatabase database; diff --git a/src/main/java/org/jabref/cli/JabRefCLI.java b/src/main/java/org/jabref/cli/JabRefCLI.java index 0cd74a0bba40..524f4f0d2778 100644 --- a/src/main/java/org/jabref/cli/JabRefCLI.java +++ b/src/main/java/org/jabref/cli/JabRefCLI.java @@ -1,10 +1,15 @@ package org.jabref.cli; -import java.util.List; -import java.util.Objects; +import com.airhacks.afterburner.injection.Injector; import javafx.util.Pair; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.l10n.Localization; @@ -15,17 +20,15 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DummyFileUpdateMonitor; -import com.airhacks.afterburner.injection.Injector; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; +import java.util.List; +import java.util.Objects; public class JabRefCLI { - private static final int WIDTH = 100; // Number of characters per line before a line break must be added. - private static final String WRAPPED_LINE_PREFIX = ""; // If a line break is added, this prefix will be inserted at the beginning of the next line + private static final int WIDTH = + 100; // Number of characters per line before a line break must be added. + private static final String WRAPPED_LINE_PREFIX = + ""; // If a line break is added, this prefix will be inserted at the beginning of the + // next line private static final String STRING_TABLE_DELIMITER = " : "; private final CommandLine cl; @@ -38,10 +41,11 @@ public JabRefCLI(String[] args) throws ParseException { } public static String getExportMatchesSyntax() { - return "[%s]searchTerm,outputFile:%s[,%s]".formatted( - Localization.lang("field"), - Localization.lang("file"), - Localization.lang("exportFormat")); + return "[%s]searchTerm,outputFile:%s[,%s]" + .formatted( + Localization.lang("field"), + Localization.lang("file"), + Localization.lang("exportFormat")); } public boolean isHelp() { @@ -165,9 +169,13 @@ public boolean isWriteMetadataToPdf() { } public String getWriteMetadataToPdf() { - return cl.hasOption("writeMetadatatoPdf") ? cl.getOptionValue("writeMetadataToPdf") : - cl.hasOption("writeXMPtoPdf") ? cl.getOptionValue("writeXmpToPdf") : - cl.hasOption("embeddBibfileInPdf") ? cl.getOptionValue("embeddBibfileInPdf") : null; + return cl.hasOption("writeMetadatatoPdf") + ? cl.getOptionValue("writeMetadataToPdf") + : cl.hasOption("writeXMPtoPdf") + ? cl.getOptionValue("writeXmpToPdf") + : cl.hasOption("embeddBibfileInPdf") + ? cl.getOptionValue("embeddBibfileInPdf") + : null; } public String getJumpToKey() { @@ -182,125 +190,196 @@ private static Options getOptions() { Options options = new Options(); // boolean options - options.addOption("h", "help", false, Localization.lang("Display help on command line options")); - options.addOption("n", "nogui", false, Localization.lang("No GUI. Only process command line options")); - options.addOption("asfl", "automaticallySetFileLinks", false, Localization.lang("Automatically set file links")); - options.addOption("g", "generateCitationKeys", false, Localization.lang("Regenerate all keys for the entries in a BibTeX file")); - options.addOption("b", "blank", false, Localization.lang("Do not open any files at startup")); + options.addOption( + "h", "help", false, Localization.lang("Display help on command line options")); + options.addOption( + "n", + "nogui", + false, + Localization.lang("No GUI. Only process command line options")); + options.addOption( + "asfl", + "automaticallySetFileLinks", + false, + Localization.lang("Automatically set file links")); + options.addOption( + "g", + "generateCitationKeys", + false, + Localization.lang("Regenerate all keys for the entries in a BibTeX file")); + options.addOption( + "b", "blank", false, Localization.lang("Do not open any files at startup")); options.addOption("v", "version", false, Localization.lang("Display version")); options.addOption(null, "debug", false, Localization.lang("Show debug level messages")); - options.addOption(Option - .builder("i") - .longOpt("import") - .desc("%s: '%s'".formatted(Localization.lang("Import file"), "-i library.bib")) - .hasArg() - .argName("FILE[,FORMAT]") - .build()); - - options.addOption(Option - .builder() - .longOpt("importToOpen") - .desc(Localization.lang("Same as --import, but will be imported to the opened tab")) - .hasArg() - .argName("FILE[,FORMAT]") - .build()); - - options.addOption(Option - .builder("ib") - .longOpt("importBibtex") - .desc("%s: '%s'".formatted(Localization.lang("Import BibTeX"), "-ib @article{entry}")) - .hasArg() - .argName("BIBTEXT_STRING") - .build()); - - options.addOption(Option - .builder("o") - .longOpt("output") - .desc("%s: '%s'".formatted(Localization.lang("Export an input to a file"), "-i db.bib -o db.htm,html")) - .hasArg() - .argName("FILE[,FORMAT]") - .build()); - - options.addOption(Option - .builder("m") - .longOpt("exportMatches") - .desc("%s: '%s'".formatted(Localization.lang("Matching"), "-i db.bib -m author=Newton,search.htm,html")) - .hasArg() - .argName("QUERY,FILE[,FORMAT]") - .build()); - - options.addOption(Option - .builder("f") - .longOpt("fetch") - .desc("%s: '%s'".formatted(Localization.lang("Run fetcher"), "-f Medline/PubMed:cancer")) - .hasArg() - .argName("FETCHER:QUERY") - .build()); - - options.addOption(Option - .builder("a") - .longOpt("aux") - .desc("%s: '%s'".formatted(Localization.lang("Sublibrary from AUX to BibTeX"), "-a thesis.aux,new.bib")) - .hasArg() - .argName("FILE[.aux],FILE[.bib] FILE") - .build()); - - options.addOption(Option - .builder("x") - .longOpt("prexp") - .desc("%s: '%s'".formatted(Localization.lang("Export preferences to a file"), "-x prefs.xml")) - .hasArg() - .argName("[FILE]") - .build()); - - options.addOption(Option - .builder("p") - .longOpt("primp") - .desc("%s: '%s'".formatted(Localization.lang("Import preferences from a file"), "-p prefs.xml")) - .hasArg() - .argName("[FILE]") - .build()); - - options.addOption(Option - .builder("d") - .longOpt("prdef") - .desc("%s: '%s'".formatted(Localization.lang("Reset preferences"), "-d mainFontSize,newline' or '-d all")) - .hasArg() - .argName("KEY1[,KEY2][,KEYn] | all") - .build()); - - options.addOption(Option - .builder() - .longOpt("writeXmpToPdf") - .desc("%s: '%s'".formatted(Localization.lang("Write BibTeX as XMP metadata to PDF."), "-w pathToMyOwnPaper.pdf")) - .hasArg() - .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") - .build()); - - options.addOption(Option - .builder() - .longOpt("embedBibFileInPdf") - .desc("%s: '%s'".formatted(Localization.lang("Embed BibTeX as attached file in PDF."), "-w pathToMyOwnPaper.pdf")) - .hasArg() - .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") - .build()); - - options.addOption(Option - .builder("w") - .longOpt("writeMetadataToPdf") - .desc("%s: '%s'".formatted(Localization.lang("Write BibTeX to PDF (XMP and embedded)"), "-w pathToMyOwnPaper.pdf")) - .hasArg() - .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") - .build()); - - options.addOption(Option - .builder("j") - .longOpt("jumpToKey") - .desc("%s: '%s'".formatted(Localization.lang("Jump to the entry of the given citation key."), "-j key")) - .hasArg() - .argName("CITATIONKEY") - .build()); + options.addOption( + Option.builder("i") + .longOpt("import") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Import file"), "-i library.bib")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption( + Option.builder() + .longOpt("importToOpen") + .desc( + Localization.lang( + "Same as --import, but will be imported to the opened tab")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption( + Option.builder("ib") + .longOpt("importBibtex") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Import BibTeX"), + "-ib @article{entry}")) + .hasArg() + .argName("BIBTEXT_STRING") + .build()); + + options.addOption( + Option.builder("o") + .longOpt("output") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Export an input to a file"), + "-i db.bib -o db.htm,html")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption( + Option.builder("m") + .longOpt("exportMatches") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Matching"), + "-i db.bib -m author=Newton,search.htm,html")) + .hasArg() + .argName("QUERY,FILE[,FORMAT]") + .build()); + + options.addOption( + Option.builder("f") + .longOpt("fetch") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Run fetcher"), + "-f Medline/PubMed:cancer")) + .hasArg() + .argName("FETCHER:QUERY") + .build()); + + options.addOption( + Option.builder("a") + .longOpt("aux") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Sublibrary from AUX to BibTeX"), + "-a thesis.aux,new.bib")) + .hasArg() + .argName("FILE[.aux],FILE[.bib] FILE") + .build()); + + options.addOption( + Option.builder("x") + .longOpt("prexp") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Export preferences to a file"), + "-x prefs.xml")) + .hasArg() + .argName("[FILE]") + .build()); + + options.addOption( + Option.builder("p") + .longOpt("primp") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Import preferences from a file"), + "-p prefs.xml")) + .hasArg() + .argName("[FILE]") + .build()); + + options.addOption( + Option.builder("d") + .longOpt("prdef") + .desc( + "%s: '%s'" + .formatted( + Localization.lang("Reset preferences"), + "-d mainFontSize,newline' or '-d all")) + .hasArg() + .argName("KEY1[,KEY2][,KEYn] | all") + .build()); + + options.addOption( + Option.builder() + .longOpt("writeXmpToPdf") + .desc( + "%s: '%s'" + .formatted( + Localization.lang( + "Write BibTeX as XMP metadata to PDF."), + "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption( + Option.builder() + .longOpt("embedBibFileInPdf") + .desc( + "%s: '%s'" + .formatted( + Localization.lang( + "Embed BibTeX as attached file in PDF."), + "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption( + Option.builder("w") + .longOpt("writeMetadataToPdf") + .desc( + "%s: '%s'" + .formatted( + Localization.lang( + "Write BibTeX to PDF (XMP and embedded)"), + "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption( + Option.builder("j") + .longOpt("jumpToKey") + .desc( + "%s: '%s'" + .formatted( + Localization.lang( + "Jump to the entry of the given citation key."), + "-j key")) + .hasArg() + .argName("CITATIONKEY") + .build()); return options; } @@ -312,33 +391,46 @@ public void displayVersion() { public static void printUsage(CliPreferences preferences) { String header = ""; - ImportFormatReader importFormatReader = new ImportFormatReader( - preferences.getImporterPreferences(), - preferences.getImportFormatPreferences(), - preferences.getCitationKeyPatternPreferences(), - new DummyFileUpdateMonitor() - ); - List> importFormats = importFormatReader - .getImportFormats().stream() - .map(format -> new Pair<>(format.getName(), format.getId())) - .toList(); + ImportFormatReader importFormatReader = + new ImportFormatReader( + preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), + preferences.getCitationKeyPatternPreferences(), + new DummyFileUpdateMonitor()); + List> importFormats = + importFormatReader.getImportFormats().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); String importFormatsIntro = Localization.lang("Available import formats"); - String importFormatsList = "%s:%n%s%n".formatted(importFormatsIntro, alignStringTable(importFormats)); - - ExporterFactory exporterFactory = ExporterFactory.create( - preferences, - Injector.instantiateModelOrService(BibEntryTypesManager.class)); - List> exportFormats = exporterFactory - .getExporters().stream() - .map(format -> new Pair<>(format.getName(), format.getId())) - .toList(); + String importFormatsList = + "%s:%n%s%n".formatted(importFormatsIntro, alignStringTable(importFormats)); + + ExporterFactory exporterFactory = + ExporterFactory.create( + preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); + List> exportFormats = + exporterFactory.getExporters().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); String outFormatsIntro = Localization.lang("Available export formats"); - String outFormatsList = "%s:%n%s%n".formatted(outFormatsIntro, alignStringTable(exportFormats)); + String outFormatsList = + "%s:%n%s%n".formatted(outFormatsIntro, alignStringTable(exportFormats)); - String footer = '\n' + importFormatsList + outFormatsList + "\nPlease report issues at https://github.com/JabRef/jabref/issues."; + String footer = + '\n' + + importFormatsList + + outFormatsList + + "\nPlease report issues at https://github.com/JabRef/jabref/issues."; HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp(WIDTH, "jabref [OPTIONS] [BIBTEX_FILE]\n\nOptions:", header, getOptions(), footer, true); + formatter.printHelp( + WIDTH, + "jabref [OPTIONS] [BIBTEX_FILE]\n\nOptions:", + header, + getOptions(), + footer, + true); } private String getVersionInfo() { @@ -353,9 +445,11 @@ public List getLeftOver() { protected static String alignStringTable(List> table) { StringBuilder sb = new StringBuilder(); - int maxLength = table.stream() - .mapToInt(pair -> Objects.requireNonNullElse(pair.getKey(), "").length()) - .max().orElse(0); + int maxLength = + table.stream() + .mapToInt(pair -> Objects.requireNonNullElse(pair.getKey(), "").length()) + .max() + .orElse(0); for (Pair pair : table) { int padding = Math.max(0, maxLength - pair.getKey().length()); diff --git a/src/main/java/org/jabref/cli/JournalListMvGenerator.java b/src/main/java/org/jabref/cli/JournalListMvGenerator.java index dad187dc81a3..779255f7ffee 100644 --- a/src/main/java/org/jabref/cli/JournalListMvGenerator.java +++ b/src/main/java/org/jabref/cli/JournalListMvGenerator.java @@ -1,5 +1,11 @@ package org.jabref.cli; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.jabref.logic.journals.Abbreviation; +import org.jabref.logic.journals.JournalAbbreviationLoader; +import org.jooq.lambda.Unchecked; + import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; @@ -9,13 +15,6 @@ import java.util.Set; import java.util.stream.Collectors; -import org.jabref.logic.journals.Abbreviation; -import org.jabref.logic.journals.JournalAbbreviationLoader; - -import org.h2.mvstore.MVMap; -import org.h2.mvstore.MVStore; -import org.jooq.lambda.Unchecked; - public class JournalListMvGenerator { public static void main(String[] args) throws IOException { @@ -23,53 +22,66 @@ public static void main(String[] args) throws IOException { Path abbreviationsDirectory = Path.of("buildres", "abbrv.jabref.org", "journals"); if (!Files.exists(abbreviationsDirectory)) { - System.out.println("Path " + abbreviationsDirectory.toAbsolutePath() + " does not exist"); + System.out.println( + "Path " + abbreviationsDirectory.toAbsolutePath() + " does not exist"); System.exit(0); } - Path journalListMvFile = Path.of("build", "resources", "main", "journals", "journal-list.mv"); + Path journalListMvFile = + Path.of("build", "resources", "main", "journals", "journal-list.mv"); - Set ignoredNames = Set.of( - // remove all lists without dot in them: - // we use abbreviation lists containing dots in them only (to be consistent) - "journal_abbreviations_entrez.csv", - "journal_abbreviations_medicus.csv", - "journal_abbreviations_webofscience-dotless.csv", + Set ignoredNames = + Set.of( + // remove all lists without dot in them: + // we use abbreviation lists containing dots in them only (to be consistent) + "journal_abbreviations_entrez.csv", + "journal_abbreviations_medicus.csv", + "journal_abbreviations_webofscience-dotless.csv", - // we currently do not have good support for BibTeX strings - "journal_abbreviations_ieee_strings.csv" - ); + // we currently do not have good support for BibTeX strings + "journal_abbreviations_ieee_strings.csv"); Files.createDirectories(journalListMvFile.getParent()); - try (DirectoryStream stream = Files.newDirectoryStream(abbreviationsDirectory, "*.csv"); - MVStore store = new MVStore.Builder(). - fileName(journalListMvFile.toString()). - compressHigh(). - open()) { + try (DirectoryStream stream = + Files.newDirectoryStream(abbreviationsDirectory, "*.csv"); + MVStore store = + new MVStore.Builder() + .fileName(journalListMvFile.toString()) + .compressHigh() + .open()) { MVMap fullToAbbreviation = store.openMap("FullToAbbreviation"); - stream.forEach(Unchecked.consumer(path -> { - String fileName = path.getFileName().toString(); - System.out.print("Checking "); - System.out.print(fileName); - if (ignoredNames.contains(fileName)) { - System.out.println(" ignored"); - } else { - System.out.println("..."); - Collection abbreviations = JournalAbbreviationLoader.readAbbreviationsFromCsvFile(path); - Map abbreviationMap = abbreviations - .stream() - .collect(Collectors.toMap( - Abbreviation::getName, - abbreviation -> abbreviation, - (abbreviation1, abbreviation2) -> { - if (verbose) { - System.out.println("Double entry " + abbreviation1.getName()); - } - return abbreviation2; - })); - fullToAbbreviation.putAll(abbreviationMap); - } - })); + stream.forEach( + Unchecked.consumer( + path -> { + String fileName = path.getFileName().toString(); + System.out.print("Checking "); + System.out.print(fileName); + if (ignoredNames.contains(fileName)) { + System.out.println(" ignored"); + } else { + System.out.println("..."); + Collection abbreviations = + JournalAbbreviationLoader.readAbbreviationsFromCsvFile( + path); + Map abbreviationMap = + abbreviations.stream() + .collect( + Collectors.toMap( + Abbreviation::getName, + abbreviation -> abbreviation, + (abbreviation1, + abbreviation2) -> { + if (verbose) { + System.out.println( + "Double entry " + + abbreviation1 + .getName()); + } + return abbreviation2; + })); + fullToAbbreviation.putAll(abbreviationMap); + } + })); } } } diff --git a/src/main/java/org/jabref/gui/ClipBoardManager.java b/src/main/java/org/jabref/gui/ClipBoardManager.java index 3e1455908e05..f1c63af8c38b 100644 --- a/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -1,12 +1,6 @@ package org.jabref.gui; -import java.awt.Toolkit; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; -import java.util.List; +import com.airhacks.afterburner.injection.Injector; import javafx.application.Platform; import javafx.scene.control.TextInputControl; @@ -23,11 +17,17 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.BibtexString; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.List; + @AllowedToUseAwt("Requires ava.awt.datatransfer.Clipboard") public class ClipBoardManager { @@ -58,19 +58,25 @@ public ClipBoardManager(Clipboard clipboard, java.awt.datatransfer.Clipboard pri * 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()); } - })); - input.setOnMouseClicked(event -> { - if (event.getButton() == MouseButton.MIDDLE) { - input.insertText(input.getCaretPosition(), getContentsPrimary()); - } - }); + }); } /** @@ -153,25 +159,39 @@ public void setContent(String string) { setPrimaryClipboardContent(content); } - public void setContent(List entries, BibEntryTypesManager entryTypesManager) throws IOException { + public void setContent(List entries, BibEntryTypesManager entryTypesManager) + throws IOException { String serializedEntries = serializeEntries(entries, entryTypesManager); 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()); } - private String serializeEntries(List entries, BibEntryTypesManager entryTypesManager) throws IOException { + 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 + // 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); + BibEntryWriter writer = + new BibEntryWriter( + new FieldWriter(preferences.getFieldPreferences()), entryTypesManager); return writer.serializeAll(entries, BibDatabaseMode.BIBTEX); } } diff --git a/src/main/java/org/jabref/gui/CoreGuiPreferences.java b/src/main/java/org/jabref/gui/CoreGuiPreferences.java index 23212a62ed19..0c949da63dff 100644 --- a/src/main/java/org/jabref/gui/CoreGuiPreferences.java +++ b/src/main/java/org/jabref/gui/CoreGuiPreferences.java @@ -19,13 +19,14 @@ public class CoreGuiPreferences { private final StringProperty lastSelectedIdBasedFetcher; - public CoreGuiPreferences(double positionX, - double positionY, - double sizeX, - double sizeY, - boolean windowMaximised, - String lastSelectedIdBasedFetcher, - double sidePaneWidth) { + public CoreGuiPreferences( + double positionX, + double positionY, + double sizeX, + double sizeY, + boolean windowMaximised, + String lastSelectedIdBasedFetcher, + double sidePaneWidth) { this.positionX = new SimpleDoubleProperty(positionX); this.positionY = new SimpleDoubleProperty(positionY); this.sizeX = new SimpleDoubleProperty(sizeX); diff --git a/src/main/java/org/jabref/gui/DialogService.java b/src/main/java/org/jabref/gui/DialogService.java index 7248be475117..bc08b1fc31fb 100644 --- a/src/main/java/org/jabref/gui/DialogService.java +++ b/src/main/java/org/jabref/gui/DialogService.java @@ -1,12 +1,5 @@ package org.jabref.gui; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; - import javafx.concurrent.Task; import javafx.print.PrinterJob; import javafx.scene.control.Alert; @@ -17,6 +10,8 @@ import javafx.scene.control.TextInputDialog; import javafx.util.StringConverter; +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.dialog.ProgressDialog; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.BaseWindow; import org.jabref.gui.util.DirectoryDialogConfiguration; @@ -24,8 +19,12 @@ import org.jabref.logic.importer.FetcherException; import org.jabref.logic.util.NotificationService; -import org.controlsfx.control.textfield.CustomPasswordField; -import org.controlsfx.dialog.ProgressDialog; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; /** * This interface provides methods to create dialogs and show them to the user. @@ -37,19 +36,37 @@ public interface DialogService extends NotificationService { * * @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 */ - 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); - - default Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, Collection choices, StringConverter converter) { - return showEditableChoiceDialogAndWait(title, content, okButtonLabel, null, choices, 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) { + return showEditableChoiceDialogAndWait( + title, content, okButtonLabel, null, choices, converter); } /** @@ -60,7 +77,8 @@ default Optional showEditableChoiceDialogAndWait(String title, String con /** * 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); + Optional showInputDialogWithDefaultAndWait( + String title, String content, String defaultValue); /** * This will create and display a new information dialog. @@ -145,7 +163,8 @@ default Optional showEditableChoiceDialogAndWait(String title, String con * * @return true if the use clicked "OK" otherwise false */ - boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, String cancelButtonLabel); + boolean showConfirmationDialogAndWait( + String title, String content, String okButtonLabel, String cancelButtonLabel); /** * Create and display a new confirmation dialog. @@ -156,8 +175,8 @@ default Optional showEditableChoiceDialogAndWait(String title, String con * * @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. @@ -168,9 +187,13 @@ boolean showConfirmationDialogWithOptOutAndWait(String title, String content, * * @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 @@ -201,8 +224,8 @@ boolean showConfirmationDialogWithOptOutAndWait(String title, String content, * * @return Optional with the pressed Button as ButtonType */ - Optional showCustomButtonDialogAndWait(Alert.AlertType type, String title, String content, - ButtonType... buttonTypes); + Optional showCustomButtonDialogAndWait( + Alert.AlertType type, String title, String content, ButtonType... buttonTypes); /** * This will create and display a new dialog showing a custom {@link DialogPane} @@ -210,7 +233,8 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String * * @return Optional with the pressed Button as ButtonType */ - Optional showCustomDialogAndWait(String title, DialogPane contentPane, ButtonType... buttonTypes); + Optional showCustomDialogAndWait( + String title, DialogPane contentPane, ButtonType... buttonTypes); /** * Shows a custom dialog and returns the result. @@ -250,7 +274,8 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String * @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 @@ -281,7 +306,8 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String * * @return the selected files or an empty {@link List} if no file has been selected */ - List showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration); + List showFileOpenDialogAndGetMultipleFiles( + FileDialogConfiguration fileDialogConfiguration); /** * Shows a new directory selection dialog. The method doesn't return until the @@ -291,7 +317,8 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String * * @return the selected directory or an empty {@link Optional} if no directory has been selected */ - Optional showDirectorySelectionDialog(DirectoryDialogConfiguration directoryDialogConfiguration); + Optional showDirectorySelectionDialog( + DirectoryDialogConfiguration directoryDialogConfiguration); /** * Displays a Print Dialog. Allow the user to update job state such as printer and settings. These changes will be diff --git a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java index 4ab27396efa0..1fe7f2e9873a 100644 --- a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java +++ b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -1,20 +1,27 @@ package org.jabref.gui; -import java.util.List; - import javafx.scene.input.DataFormat; import org.jabref.logic.preview.PreviewLayout; +import java.util.List; + /** * Contains all the different {@link DataFormat}s that may occur in JabRef. */ 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; + 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; } diff --git a/src/main/java/org/jabref/gui/FXDialog.java b/src/main/java/org/jabref/gui/FXDialog.java index 891eae589f7d..a16ea0d77eb3 100644 --- a/src/main/java/org/jabref/gui/FXDialog.java +++ b/src/main/java/org/jabref/gui/FXDialog.java @@ -1,5 +1,7 @@ package org.jabref.gui; +import com.airhacks.afterburner.injection.Injector; + import javafx.fxml.FXMLLoader; import javafx.scene.control.Alert; import javafx.scene.control.Dialog; @@ -12,8 +14,6 @@ import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; -import com.airhacks.afterburner.injection.Injector; - /** * This class provides a super class for all dialogs implemented in JavaFX. *

@@ -59,12 +59,17 @@ public FXDialog(AlertType type, boolean isModal) { initModality(Modality.NONE); } - dialogWindow.getScene().setOnKeyPressed(event -> { - KeyBindingRepository keyBindingRepository = Injector.instantiateModelOrService(KeyBindingRepository.class); - if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.CLOSE, event)) { - dialogWindow.close(); - } - }); + dialogWindow + .getScene() + .setOnKeyPressed( + event -> { + KeyBindingRepository keyBindingRepository = + Injector.instantiateModelOrService(KeyBindingRepository.class); + if (keyBindingRepository.checkKeyCombinationEquality( + KeyBinding.CLOSE, event)) { + dialogWindow.close(); + } + }); } public FXDialog(AlertType type) { diff --git a/src/main/java/org/jabref/gui/JabRefDialogService.java b/src/main/java/org/jabref/gui/JabRefDialogService.java index dccdc17898dd..0e1c39925714 100644 --- a/src/main/java/org/jabref/gui/JabRefDialogService.java +++ b/src/main/java/org/jabref/gui/JabRefDialogService.java @@ -1,14 +1,6 @@ package org.jabref.gui; -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; +import com.tobiasdiez.easybind.EasyBind; import javafx.concurrent.Task; import javafx.geometry.Pos; @@ -37,6 +29,11 @@ import javafx.util.Duration; import javafx.util.StringConverter; +import org.controlsfx.control.Notifications; +import org.controlsfx.control.TaskProgressView; +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.dialog.ExceptionDialog; +import org.controlsfx.dialog.ProgressDialog; import org.jabref.gui.help.ErrorConsoleAction; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.theme.ThemeManager; @@ -51,16 +48,19 @@ import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.FetcherServerException; import org.jabref.logic.l10n.Localization; - -import com.tobiasdiez.easybind.EasyBind; -import org.controlsfx.control.Notifications; -import org.controlsfx.control.TaskProgressView; -import org.controlsfx.control.textfield.CustomPasswordField; -import org.controlsfx.dialog.ExceptionDialog; -import org.controlsfx.dialog.ProgressDialog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + /** * This class provides methods to create default * JavaFX dialogs which will also work on top of Swing @@ -97,26 +97,29 @@ 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 // Use the supplied callback for the action of the checkbox - alert.setDialogPane(new DialogPane() { - @Override - protected Node createDetailsButton() { - CheckBox optOut = new CheckBox(); - optOut.setText(optOutMessage); - optOut.setOnAction(e -> optOutAction.accept(optOut.isSelected())); - return optOut; - } - }); - - // Fool the dialog into thinking there is some expandable content; a group won't take up any space if it has no children + alert.setDialogPane( + new DialogPane() { + @Override + protected Node createDetailsButton() { + CheckBox optOut = new CheckBox(); + optOut.setText(optOutMessage); + optOut.setOnAction(e -> optOutAction.accept(optOut.isSelected())); + return optOut; + } + }); + + // Fool the dialog into thinking there is some expandable content; a group won't take up any + // space if it has no children alert.getDialogPane().setExpandableContent(new Group()); alert.getDialogPane().setExpanded(true); @@ -136,9 +139,16 @@ 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()); + ((Stage) choiceDialog.getDialogPane().getScene().getWindow()) + .getIcons() + .add(IconTheme.getJabRefImage()); ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); choiceDialog.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, okButtonType); choiceDialog.setHeaderText(title); @@ -149,17 +159,32 @@ private ChoiceDialog createChoiceDialog(String title, String content, Str } @Override - public Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices) { - return createChoiceDialog(title, content, okButtonLabel, defaultChoice, choices).showAndWait(); + 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) { - ChoiceDialog choiceDialog = createChoiceDialog(title, content, okButtonLabel, defaultChoice, choices); + 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); comboBox.setConverter(converter); - EasyBind.subscribe(comboBox.getEditor().textProperty(), text -> comboBox.setValue(converter.fromString(text))); + EasyBind.subscribe( + comboBox.getEditor().textProperty(), + text -> comboBox.setValue(converter.fromString(text))); return choiceDialog.showAndWait(); } @@ -173,7 +198,8 @@ public Optional showInputDialogAndWait(String title, String content) { } @Override - public Optional showInputDialogWithDefaultAndWait(String title, String content, String defaultValue) { + public Optional showInputDialogWithDefaultAndWait( + String title, String content, String defaultValue) { TextInputDialog inputDialog = new TextInputDialog(defaultValue); inputDialog.setHeaderText(title); inputDialog.setContentText(content); @@ -224,12 +250,23 @@ 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); + 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); + 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); + 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); } @@ -246,7 +283,8 @@ public void showErrorDialogAndWait(String title, String content, Throwable excep @Override public void showErrorDialogAndWait(String message) { - FXDialog alert = createDialog(AlertType.ERROR, Localization.lang("Error Occurred"), message); + FXDialog alert = + createDialog(AlertType.ERROR, Localization.lang("Error Occurred"), message); alert.showAndWait(); } @@ -257,7 +295,8 @@ public boolean showConfirmationDialogAndWait(String title, String content) { } @Override - public boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel) { + public boolean showConfirmationDialogAndWait( + String title, String content, String okButtonLabel) { FXDialog alert = createDialog(AlertType.CONFIRMATION, title, content); ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); alert.getButtonTypes().setAll(ButtonType.CANCEL, okButtonType); @@ -265,8 +304,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); @@ -275,17 +314,21 @@ 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); @@ -294,16 +337,16 @@ public boolean showConfirmationDialogWithOptOutAndWait(String title, String cont } @Override - public Optional showCustomButtonDialogAndWait(AlertType type, String title, String content, - ButtonType... buttonTypes) { + public Optional showCustomButtonDialogAndWait( + AlertType type, String title, String content, ButtonType... buttonTypes) { FXDialog alert = createDialog(type, title, content); alert.getButtonTypes().setAll(buttonTypes); return alert.showAndWait(); } @Override - public Optional showCustomDialogAndWait(String title, DialogPane contentPane, - ButtonType... buttonTypes) { + public Optional showCustomDialogAndWait( + String title, DialogPane contentPane, ButtonType... buttonTypes) { FXDialog alert = new FXDialog(AlertType.NONE, title); alert.setDialogPane(contentPane); alert.getButtonTypes().setAll(buttonTypes); @@ -336,12 +379,13 @@ public Optional showPasswordDialogAndWait(String title, String header, S dialog.getDialogPane().setContent(box); dialog.getDialogPane().getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK); - dialog.setResultConverter(dialogButton -> { - if (dialogButton == ButtonType.OK) { - return passwordField.getText(); - } - return null; - }); + dialog.setResultConverter( + dialogButton -> { + if (dialogButton == ButtonType.OK) { + return passwordField.getText(); + } + return null; + }); return dialog.showAndWait(); } @@ -351,15 +395,18 @@ private ProgressDialog createProgressDialog(String title, String content, Ta progressDialog.setTitle(title); progressDialog.setContentText(content); progressDialog.setGraphic(null); - ((Stage) progressDialog.getDialogPane().getScene().getWindow()).getIcons().add(IconTheme.getJabRefImage()); + ((Stage) progressDialog.getDialogPane().getScene().getWindow()) + .getIcons() + .add(IconTheme.getJabRefImage()); progressDialog.setOnCloseRequest(evt -> task.cancel()); DialogPane dialogPane = progressDialog.getDialogPane(); dialogPane.getButtonTypes().add(ButtonType.CANCEL); Button cancelButton = (Button) dialogPane.lookupButton(ButtonType.CANCEL); - cancelButton.setOnAction(evt -> { - task.cancel(); - progressDialog.close(); - }); + cancelButton.setOnAction( + evt -> { + task.cancel(); + progressDialog.close(); + }); progressDialog.initOwner(mainWindow); return progressDialog; } @@ -377,11 +424,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); @@ -397,12 +446,15 @@ public Optional showBackgroundProgressDialogAndWait(String title, St alert.setResizable(true); alert.initOwner(mainWindow); - stateManager.getAnyTasksThatWillNotBeRecoveredRunning().addListener((observable, oldValue, newValue) -> { - if (!newValue) { - alert.setResult(ButtonType.YES); - alert.close(); - } - }); + stateManager + .getAnyTasksThatWillNotBeRecoveredRunning() + .addListener( + (observable, oldValue, newValue) -> { + if (!newValue) { + alert.setResult(ButtonType.YES); + alert.close(); + } + }); return alert.showAndWait(); } @@ -413,31 +465,40 @@ public void notify(String message) { // 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); } @@ -445,27 +506,34 @@ 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); } @Override - public Optional showDirectorySelectionDialog(DirectoryDialogConfiguration directoryDialogConfiguration) { + public Optional showDirectorySelectionDialog( + DirectoryDialogConfiguration directoryDialogConfiguration) { DirectoryChooser chooser = getConfiguredDirectoryChooser(directoryDialogConfiguration); File file = chooser.showDialog(mainWindow); return Optional.ofNullable(file).map(File::toPath); } @Override - public List showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration) { + public List showFileOpenDialogAndGetMultipleFiles( + FileDialogConfiguration fileDialogConfiguration) { FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); List files = chooser.showOpenMultipleDialog(mainWindow); return files != null ? files.stream().map(File::toPath).toList() : List.of(); } - private DirectoryChooser getConfiguredDirectoryChooser(DirectoryDialogConfiguration directoryDialogConfiguration) { + private DirectoryChooser getConfiguredDirectoryChooser( + DirectoryDialogConfiguration directoryDialogConfiguration) { DirectoryChooser chooser = new DirectoryChooser(); - directoryDialogConfiguration.getInitialDirectory().map(Path::toFile).ifPresent(chooser::setInitialDirectory); + directoryDialogConfiguration + .getInitialDirectory() + .map(Path::toFile) + .ifPresent(chooser::setInitialDirectory); return chooser; } @@ -474,7 +542,10 @@ private FileChooser getConfiguredFileChooser(FileDialogConfiguration fileDialogC chooser.getExtensionFilters().addAll(fileDialogConfiguration.getExtensionFilters()); chooser.setSelectedExtensionFilter(fileDialogConfiguration.getDefaultExtension()); chooser.setInitialFileName(fileDialogConfiguration.getInitialFileName()); - fileDialogConfiguration.getInitialDirectory().map(Path::toFile).ifPresent(chooser::setInitialDirectory); + fileDialogConfiguration + .getInitialDirectory() + .map(Path::toFile) + .ifPresent(chooser::setInitialDirectory); return chooser; } @@ -512,13 +583,17 @@ 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."); + 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."); + 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."); + 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."); + Localization.lang( + "Something is wrong on JabRef side. Please check the URL and try again."); }; } } diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index b0c0e1c83f0e..fda21561e73b 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -1,9 +1,7 @@ package org.jabref.gui; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; import javafx.application.Application; import javafx.application.Platform; @@ -14,6 +12,8 @@ import javafx.stage.Stage; import javafx.stage.WindowEvent; +import kong.unirest.core.Unirest; + import org.jabref.gui.ai.chatting.chathistory.ChatHistoryService; import org.jabref.gui.frame.JabRefFrame; import org.jabref.gui.help.VersionWorker; @@ -41,13 +41,14 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.injection.Injector; -import com.tobiasdiez.easybind.EasyBind; -import kong.unirest.core.Unirest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + /** * Represents the outer stage and the scene of the JabRef window. */ @@ -75,9 +76,10 @@ public class JabRefGUI extends Application { private Stage mainStage; - public static void setup(List uiCommands, - GuiPreferences preferences, - FileUpdateMonitor fileUpdateMonitor) { + public static void setup( + List uiCommands, + GuiPreferences preferences, + FileUpdateMonitor fileUpdateMonitor) { JabRefGUI.uiCommands = uiCommands; JabRefGUI.preferences = preferences; JabRefGUI.fileUpdateMonitor = fileUpdateMonitor; @@ -87,27 +89,32 @@ public static void setup(List uiCommands, public void start(Stage stage) { this.mainStage = stage; - FallbackExceptionHandler.installExceptionHandler((exception, thread) -> { - UiTaskExecutor.runInJavaFXThread(() -> { - DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); - dialogService.showErrorDialogAndWait("Uncaught exception occurred in " + thread, exception); - }); - }); + FallbackExceptionHandler.installExceptionHandler( + (exception, thread) -> { + UiTaskExecutor.runInJavaFXThread( + () -> { + DialogService dialogService = + Injector.instantiateModelOrService(DialogService.class); + dialogService.showErrorDialogAndWait( + "Uncaught exception occurred in " + thread, exception); + }); + }); initialize(); - JabRefGUI.mainFrame = new JabRefFrame( - mainStage, - dialogService, - fileUpdateMonitor, - preferences, - aiService, - chatHistoryService, - stateManager, - countingUndoManager, - Injector.instantiateModelOrService(BibEntryTypesManager.class), - clipBoardManager, - taskExecutor); + JabRefGUI.mainFrame = + new JabRefFrame( + mainStage, + dialogService, + fileUpdateMonitor, + preferences, + aiService, + chatHistoryService, + stateManager, + countingUndoManager, + Injector.instantiateModelOrService(BibEntryTypesManager.class), + clipBoardManager, + taskExecutor); openWindow(); @@ -115,21 +122,22 @@ public void start(Stage stage) { 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.")); + 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(); - } - }); + EasyBind.subscribe( + preferences.getInternalPreferences().versionCheckEnabledProperty(), + enabled -> { + if (enabled) { + new VersionWorker( + buildInfo.version, dialogService, taskExecutor, preferences) + .checkForNewVersionDelayed(); + } + }); setupProxy(); } @@ -143,12 +151,12 @@ public void initialize() { JabRefGUI.stateManager = new StateManager(); Injector.setModelOrService(StateManager.class, stateManager); - Injector.setModelOrService(KeyBindingRepository.class, preferences.getKeyBindingRepository()); + Injector.setModelOrService( + KeyBindingRepository.class, preferences.getKeyBindingRepository()); - JabRefGUI.themeManager = new ThemeManager( - preferences.getWorkspacePreferences(), - fileUpdateMonitor, - Runnable::run); + JabRefGUI.themeManager = + new ThemeManager( + preferences.getWorkspacePreferences(), fileUpdateMonitor, Runnable::run); Injector.setModelOrService(ThemeManager.class, themeManager); JabRefGUI.countingUndoManager = new CountingUndoManager(); @@ -165,16 +173,17 @@ public void initialize() { JabRefGUI.clipBoardManager = new ClipBoardManager(); Injector.setModelOrService(ClipBoardManager.class, clipBoardManager); - JabRefGUI.aiService = new AiService( - preferences.getAiPreferences(), - preferences.getFilePreferences(), - dialogService, - taskExecutor); + JabRefGUI.aiService = + new AiService( + preferences.getAiPreferences(), + preferences.getFilePreferences(), + dialogService, + taskExecutor); Injector.setModelOrService(AiService.class, aiService); - JabRefGUI.chatHistoryService = new ChatHistoryService( - preferences.getCitationKeyPatternPreferences(), - dialogService); + JabRefGUI.chatHistoryService = + new ChatHistoryService( + preferences.getCitationKeyPatternPreferences(), dialogService); Injector.setModelOrService(ChatHistoryService.class, chatHistoryService); } @@ -190,10 +199,11 @@ 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()); @@ -212,7 +222,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()); @@ -226,7 +237,8 @@ private void openWindow() { 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."); + 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()); @@ -245,10 +257,11 @@ private void openWindow() { themeManager.installCss(scene); LOGGER.debug("Handle TextEditor key bindings"); - scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> TextInputKeyBindings.call( - scene, - event, - preferences.getKeyBindingRepository())); + scene.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> + TextInputKeyBindings.call( + scene, event, preferences.getKeyBindingRepository())); mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); @@ -270,7 +283,7 @@ 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(); } } @@ -305,7 +318,8 @@ private void saveWindowState() { * @param mainStage JabRef's stage */ private void debugLogWindowState(Stage mainStage) { - LOGGER.debug(""" + LOGGER.debug( + """ screen data: mainStage.WINDOW_MAXIMISED: {} mainStage.POS_X: {} @@ -313,7 +327,11 @@ private void debugLogWindowState(Stage mainStage) { mainStage.SIZE_X: {} mainStage.SIZE_Y: {} """, - mainStage.isMaximized(), mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()); + mainStage.isMaximized(), + mainStage.getX(), + mainStage.getY(), + mainStage.getWidth(), + mainStage.getHeight()); } /** @@ -323,7 +341,8 @@ private boolean isWindowPositionInBounds() { CoreGuiPreferences coreGuiPreferences = preferences.getGuiPreferences(); if (LOGGER.isDebugEnabled()) { - Screen.getScreens().forEach(screen -> LOGGER.debug("Screen bounds: {}", screen.getBounds())); + Screen.getScreens() + .forEach(screen -> LOGGER.debug("Screen bounds: {}", screen.getBounds())); } return lowerLeftIsInBounds(coreGuiPreferences) && upperRightIsInBounds(coreGuiPreferences); @@ -335,19 +354,24 @@ private boolean lowerLeftIsInBounds(CoreGuiPreferences coreGuiPreferences) { double bottomY = coreGuiPreferences.getPositionY() + coreGuiPreferences.getSizeY(); LOGGER.debug("left x: {}, bottom y: {}", leftX, bottomY); - boolean inBounds = Screen.getScreens().stream().anyMatch((screen -> screen.getBounds().contains(leftX, bottomY))); + boolean inBounds = + Screen.getScreens().stream() + .anyMatch((screen -> screen.getBounds().contains(leftX, bottomY))); LOGGER.debug("lower left corner is in bounds: {}", inBounds); return inBounds; } 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 + // 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); - boolean inBounds = Screen.getScreens().stream().anyMatch((screen -> screen.getBounds().contains(rightX, topY))); + boolean inBounds = + Screen.getScreens().stream() + .anyMatch((screen -> screen.getBounds().contains(rightX, topY))); LOGGER.debug("upper right corner is in bounds: {}", inBounds); return inBounds; } @@ -355,14 +379,12 @@ private boolean upperRightIsInBounds(CoreGuiPreferences coreGuiPreferences) { // Background tasks public void startBackgroundTasks() { RemotePreferences remotePreferences = preferences.getRemotePreferences(); - BibEntryTypesManager bibEntryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); + BibEntryTypesManager bibEntryTypesManager = + Injector.instantiateModelOrService(BibEntryTypesManager.class); if (remotePreferences.useRemoteServer()) { remoteListenerServerManager.openAndStart( new CLIMessageHandler( - mainFrame, - preferences, - fileUpdateMonitor, - bibEntryTypesManager), + mainFrame, preferences, fileUpdateMonitor, bibEntryTypesManager), remotePreferences.getPort()); } } @@ -398,7 +420,8 @@ public static void shutdownThreadPools() { LOGGER.trace("Shutting down fileUpdateMonitor"); fileUpdateMonitor.shutdown(); LOGGER.trace("Shutting down directoryMonitor"); - DirectoryMonitor directoryMonitor = Injector.instantiateModelOrService(DirectoryMonitor.class); + DirectoryMonitor directoryMonitor = + Injector.instantiateModelOrService(DirectoryMonitor.class); directoryMonitor.shutdown(); LOGGER.trace("Shutting down HeadlessExecutorService"); HeadlessExecutorService.INSTANCE.shutdownEverything(); diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 791eec0dee12..ca614a428e9f 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -1,16 +1,9 @@ package org.jabref.gui; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Random; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; +import com.google.common.eventbus.Subscribe; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; import javafx.animation.PauseTransition; import javafx.application.Platform; @@ -36,6 +29,8 @@ import javafx.scene.layout.BorderPane; import javafx.util.Duration; +import org.controlsfx.control.NotificationPane; +import org.controlsfx.control.action.Action; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; @@ -98,16 +93,21 @@ import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.injection.Injector; -import com.google.common.eventbus.Subscribe; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.Subscription; -import org.controlsfx.control.NotificationPane; -import org.controlsfx.control.action.Action; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + /** * Represents the ui area where the notifier pane, the library table and the entry editor are shown. */ @@ -115,7 +115,10 @@ public class LibraryTab extends Tab { /** * 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 + } private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); private final LibraryTabContainer tabContainer; @@ -155,7 +158,8 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private Subscription dividerPositionSubscription; private ListProperty selectedGroupsProperty; - private final OptionalObjectProperty searchQueryProperty = OptionalObjectProperty.empty(); + private final OptionalObjectProperty searchQueryProperty = + OptionalObjectProperty.empty(); private final IntegerProperty resultSize = new SimpleIntegerProperty(0); private Optional changeMonitor = Optional.empty(); @@ -175,17 +179,18 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } * 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(BibDatabaseContext bibDatabaseContext, - LibraryTabContainer tabContainer, - DialogService dialogService, - GuiPreferences preferences, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor, - boolean isDummyContext) { + private LibraryTab( + BibDatabaseContext bibDatabaseContext, + LibraryTabContainer tabContainer, + DialogService dialogService, + GuiPreferences preferences, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor, + boolean isDummyContext) { this.tabContainer = Objects.requireNonNull(tabContainer); this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); this.undoManager = undoManager; @@ -196,7 +201,9 @@ private LibraryTab(BibDatabaseContext bibDatabaseContext, this.entryTypesManager = entryTypesManager; this.clipBoardManager = clipBoardManager; this.taskExecutor = taskExecutor; - this.directoryMonitorManager = new DirectoryMonitorManager(Injector.instantiateModelOrService(DirectoryMonitor.class)); + this.directoryMonitorManager = + new DirectoryMonitorManager( + Injector.instantiateModelOrService(DirectoryMonitor.class)); initializeComponentsAndListeners(isDummyContext); @@ -220,19 +227,31 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); - this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); - this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, stateManager, getLuceneManager(), selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty()); + this.selectedGroupsProperty = + new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); + this.tableModel = + new MainTableDataModel( + getBibDatabaseContext(), + preferences, + taskExecutor, + stateManager, + getLuceneManager(), + selectedGroupsProperty(), + searchQueryProperty(), + resultSizeProperty()); new CitationStyleCache(bibDatabaseContext); - annotationCache = new FileAnnotationCache(bibDatabaseContext, preferences.getFilePreferences()); - importHandler = new ImportHandler( - bibDatabaseContext, - preferences, - fileUpdateMonitor, - undoManager, - stateManager, - dialogService, - taskExecutor); + annotationCache = + new FileAnnotationCache(bibDatabaseContext, preferences.getFilePreferences()); + importHandler = + new ImportHandler( + bibDatabaseContext, + preferences, + fileUpdateMonitor, + undoManager, + stateManager, + dialogService, + taskExecutor); setupMainPanel(); setupAutoCompletion(); @@ -249,17 +268,23 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { this.entryEditor = createEntryEditor(); - Platform.runLater(() -> { - EasyBind.subscribe(changedProperty, this::updateTabTitle); - stateManager.getOpenDatabases().addListener((ListChangeListener) c -> - updateTabTitle(changedProperty.getValue())); - }); + Platform.runLater( + () -> { + EasyBind.subscribe(changedProperty, this::updateTabTitle); + stateManager + .getOpenDatabases() + .addListener( + (ListChangeListener) + c -> updateTabTitle(changedProperty.getValue())); + }); } private EntryEditor createEntryEditor() { Supplier tabSupplier = () -> this; - return new EntryEditor(this, - // Actions are recreated here since this avoids passing more parameters and the amount of additional memory consumption is neglegtable. + return new EntryEditor( + this, + // Actions are recreated here since this avoids passing more parameters and the + // amount of additional memory consumption is neglegtable. new UndoAction(tabSupplier, undoManager, dialogService, stateManager), new RedoAction(tabSupplier, undoManager, dialogService, stateManager)); } @@ -275,7 +300,8 @@ private static void addModeInfo(StringBuilder text, BibDatabaseContext bibDataba text.append(modeInfo); } - private static void addSharedDbInformation(StringBuilder text, BibDatabaseContext bibDatabaseContext) { + private static void addSharedDbInformation( + StringBuilder text, BibDatabaseContext bibDatabaseContext) { text.append(bibDatabaseContext.getDBMSSynchronizer().getDBName()); text.append(" ["); text.append(Localization.lang("shared")); @@ -291,7 +317,8 @@ private void setDataLoadingTask(BackgroundTask dataLoadingTask) { * The layout to display in the tab when it is loading */ private Node createLoadingAnimationLayout() { - ProgressIndicator progressIndicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); + ProgressIndicator progressIndicator = + new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); BorderPane pane = new BorderPane(); pane.setCenter(progressIndicator); return pane; @@ -316,7 +343,9 @@ private void onDatabaseLoadingSucceed(ParserResult result) { } public void createLuceneManager() { - luceneManager = new LuceneManager(bibDatabaseContext, taskExecutor, preferences.getFilePreferences()); + luceneManager = + new LuceneManager( + bibDatabaseContext, taskExecutor, preferences.getFilePreferences()); stateManager.setLuceneManager(bibDatabaseContext, luceneManager); } @@ -332,7 +361,10 @@ private void onDatabaseLoadingFailed(Exception ex) { loading.set(false); String title = Localization.lang("Connection error"); - String content = "%s\n\n%s".formatted(ex.getMessage(), Localization.lang("A local copy will be opened.")); + String content = + "%s\n\n%s" + .formatted( + ex.getMessage(), Localization.lang("A local copy will be opened.")); dialogService.showErrorDialogAndWait(title, content, ex); } @@ -349,9 +381,14 @@ private void setDatabaseContext(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(); - foundExistingBibDatabase.ifPresent(databaseContext -> stateManager.getOpenDatabases().remove(databaseContext)); + // 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 = Objects.requireNonNull(bibDatabaseContext); @@ -364,22 +401,29 @@ private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) { public void installAutosaveManagerAndBackupManager() { if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { AutosaveManager autosaveManager = AutosaveManager.start(bibDatabaseContext); - autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); + autosaveManager.registerListener( + new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); } - if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { - BackupManager.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); + if (isDatabaseReadyForBackup(bibDatabaseContext) + && preferences.getFilePreferences().shouldCreateBackup()) { + BackupManager.start( + this, + bibDatabaseContext, + Injector.instantiateModelOrService(BibEntryTypesManager.class), + preferences); } } private boolean isDatabaseReadyForAutoSave(BibDatabaseContext context) { return ((context.getLocation() == DatabaseLocation.SHARED) - || ((context.getLocation() == DatabaseLocation.LOCAL) - && preferences.getLibraryPreferences().shouldAutoSave())) + || ((context.getLocation() == DatabaseLocation.LOCAL) + && preferences.getLibraryPreferences().shouldAutoSave())) && context.getDatabasePath().isPresent(); } private boolean isDatabaseReadyForBackup(BibDatabaseContext context) { - return (context.getLocation() == DatabaseLocation.LOCAL) && context.getDatabasePath().isPresent(); + return (context.getLocation() == DatabaseLocation.LOCAL) + && context.getDatabasePath().isPresent(); } /** @@ -426,7 +470,9 @@ public void updateTabTitle(boolean isChanged) { } // Unique path fragment - Optional uniquePathPart = FileUtil.getUniquePathDirectory(stateManager.collectAllDatabasePaths(), databasePath); + Optional uniquePathPart = + FileUtil.getUniquePathDirectory( + stateManager.collectAllDatabasePaths(), databasePath); uniquePathPart.ifPresent(part -> tabTitle.append(" \u2013 ").append(part)); } else { if (databaseLocation == DatabaseLocation.LOCAL) { @@ -437,15 +483,17 @@ public void updateTabTitle(boolean isChanged) { addSharedDbInformation(toolTipText, bibDatabaseContext); } addModeInfo(toolTipText, bibDatabaseContext); - if ((databaseLocation == DatabaseLocation.LOCAL) && bibDatabaseContext.getDatabase().hasEntries()) { + if ((databaseLocation == DatabaseLocation.LOCAL) + && bibDatabaseContext.getDatabase().hasEntries()) { addChangedInformation(toolTipText, Localization.lang("untitled")); } } - UiTaskExecutor.runInJavaFXThread(() -> { - textProperty().setValue(tabTitle.toString()); - setTooltip(new Tooltip(toolTipText.toString())); - }); + UiTaskExecutor.runInJavaFXThread( + () -> { + textProperty().setValue(tabTitle.toString()); + setTooltip(new Tooltip(toolTipText.toString())); + }); } @Subscribe @@ -473,36 +521,44 @@ public void registerUndoableChanges(List changes) { public void editEntryAndFocusField(BibEntry entry, Field field) { showAndEdit(entry); - Platform.runLater(() -> { - // Focus field and entry in main table (async to give entry editor time to load) - entryEditor.setFocusToField(field); - clearAndSelect(entry); - }); + Platform.runLater( + () -> { + // Focus field and entry in main table (async to give entry editor time to load) + entryEditor.setFocusToField(field); + clearAndSelect(entry); + }); } private void createMainTable() { - 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 - mainTable.addSelectionListener(event -> { - List entries = event.getList().stream().map(BibEntryTableViewModel::getEntry).toList(); - stateManager.setSelectedEntries(entries); - if (!entries.isEmpty()) { - // Update entry editor and preview according to selected entries - entryEditor.setCurrentlyEditedEntry(entries.getFirst()); - } - }); + 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 + mainTable.addSelectionListener( + event -> { + List entries = + event.getList().stream().map(BibEntryTableViewModel::getEntry).toList(); + stateManager.setSelectedEntries(entries); + if (!entries.isEmpty()) { + // Update entry editor and preview according to selected entries + entryEditor.setCurrentlyEditedEntry(entries.getFirst()); + } + }); } public void setupMainPanel() { @@ -516,10 +572,12 @@ public void setupMainPanel() { setContent(databaseNotificationPane); // Saves the divider position as soon as it changes - // We need to keep a reference to the subscription, otherwise the binding gets garbage collected - dividerPositionSubscription = EasyBind.valueAt(splitPane.getDividers(), 0) - .mapObservable(SplitPane.Divider::positionProperty) - .subscribeToValues(this::saveDividerLocation); + // We need to keep a reference to the subscription, otherwise the binding gets garbage + // collected + dividerPositionSubscription = + EasyBind.valueAt(splitPane.getDividers(), 0) + .mapObservable(SplitPane.Divider::positionProperty) + .subscribeToValues(this::saveDividerLocation); // Add changePane in case a file is present - otherwise just add the splitPane to the panel Optional file = bibDatabaseContext.getDatabasePath(); @@ -541,15 +599,17 @@ public void setupMainPanel() { private void setupAutoCompletion() { AutoCompletePreferences autoCompletePreferences = preferences.getAutoCompletePreferences(); if (autoCompletePreferences.shouldAutoComplete()) { - suggestionProviders = new SuggestionProviders( - getDatabase(), - Injector.instantiateModelOrService(JournalAbbreviationRepository.class), - autoCompletePreferences); + suggestionProviders = + new SuggestionProviders( + getDatabase(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), + autoCompletePreferences); } else { // Create empty suggestion providers if auto-completion is deactivated suggestionProviders = new SuggestionProviders(); } - searchAutoCompleter = new PersonNameSuggestionProvider(FieldFactory.getPersonNameFields(), getDatabase()); + searchAutoCompleter = + new PersonNameSuggestionProvider(FieldFactory.getPersonNameFields(), getDatabase()); } public SuggestionProvider getAutoCompleter() { @@ -569,7 +629,8 @@ public void showAndEdit(BibEntry entry) { if (!splitPane.getItems().contains(entryEditor)) { splitPane.getItems().addLast(entryEditor); mode = PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR; - splitPane.setDividerPositions(preferences.getEntryEditorPreferences().getDividerPosition()); + splitPane.setDividerPositions( + preferences.getEntryEditorPreferences().getDividerPosition()); } // We use != instead of equals because of performance reasons @@ -596,11 +657,15 @@ public void clearAndSelect(final BibEntry bibEntry) { } public void selectPreviousEntry() { - mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() - 1); + mainTable + .getSelectionModel() + .clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() - 1); } public void selectNextEntry() { - mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() + 1); + mainTable + .getSelectionModel() + .clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() + 1); } /** @@ -617,7 +682,8 @@ public void entryEditorClosing() { private void ensureNotShowingBottomPanel(List entriesToCheck) { // This method is not able to close the bottom pane currently - if ((mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) && (entriesToCheck.contains(entryEditor.getCurrentlyEditedEntry()))) { + if ((mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) + && (entriesToCheck.contains(entryEditor.getCurrentlyEditedEntry()))) { closeBottomPane(); } } @@ -660,7 +726,10 @@ 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"); } @@ -709,20 +778,29 @@ private boolean confirmClose() { return true; } - 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); - - Optional response = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.CONFIRMATION, - Localization.lang("Save before closing"), - Localization.lang("Library '%0' has changed.", filename), - saveChanges, discardChanges, returnToLibrary); + 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); + + Optional response = + dialogService.showCustomButtonDialogAndWait( + Alert.AlertType.CONFIRMATION, + Localization.lang("Save before closing"), + Localization.lang("Library '%0' has changed.", filename), + saveChanges, + discardChanges, + returnToLibrary); if (response.isEmpty()) { return true; @@ -736,7 +814,12 @@ private boolean confirmClose() { if (buttonType.equals(saveChanges)) { try { - SaveDatabaseAction saveAction = new SaveDatabaseAction(this, dialogService, preferences, Injector.instantiateModelOrService(BibEntryTypesManager.class)); + SaveDatabaseAction saveAction = + new SaveDatabaseAction( + this, + dialogService, + preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); if (saveAction.save()) { return true; } @@ -744,14 +827,18 @@ private boolean confirmClose() { dialogService.notify(Localization.lang("Unable to save library")); } catch (Throwable ex) { LOGGER.error("A problem occurred when trying to save the file", ex); - dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + dialogService.showErrorDialogAndWait( + Localization.lang("Save library"), + Localization.lang("Could not save file."), + ex); } // Save was cancelled or an error occurred. return false; } if (buttonType.equals(discardChanges)) { - BackupManager.discardBackup(bibDatabaseContext, preferences.getFilePreferences().getBackupDirectory()); + BackupManager.discardBackup( + bibDatabaseContext, preferences.getFilePreferences().getBackupDirectory()); return true; } @@ -799,7 +886,8 @@ private void onClosed(Event event) { LOGGER.error("Problem when shutting down autosave manager", e); } try { - BackupManager.shutdown(bibDatabaseContext, + BackupManager.shutdown( + bibDatabaseContext, preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); } catch (RuntimeException e) { @@ -868,14 +956,17 @@ public FileAnnotationCache getAnnotationCache() { public void resetChangeMonitor() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); - 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) { @@ -887,7 +978,8 @@ public void insertEntries(final List entries) { importHandler.importCleanedEntries(entries); // Create an UndoableInsertEntries object. - getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); + getUndoManager() + .addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); markBaseChanged(); if (preferences.getEntryEditorPreferences().shouldOpenOnNewEntry()) { @@ -908,12 +1000,14 @@ public void copyEntry() { private int doCopyEntry(List selectedEntries) { if (!selectedEntries.isEmpty()) { - List stringConstants = bibDatabaseContext.getDatabase().getUsedStrings(selectedEntries); + List stringConstants = + bibDatabaseContext.getDatabase().getUsedStrings(selectedEntries); try { if (stringConstants.isEmpty()) { clipBoardManager.setContent(selectedEntries, entryTypesManager); } else { - clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants); + clipBoardManager.setContent( + selectedEntries, entryTypesManager, stringConstants); } return selectedEntries.size(); } catch (IOException e) { @@ -942,12 +1036,15 @@ 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")); + 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")); + dialogService.showInformationDialogAndWait( + Localization.lang("Look up identifier"), + Localization.lang("Server not available")); } else { dialogService.showErrorDialogAndWait(exception); } @@ -976,7 +1073,8 @@ public void cutEntry() { * Removes the selected entries and files linked to selected entries from the database */ public void deleteEntry() { - int entriesDeleted = doDeleteEntry(StandardActions.DELETE_ENTRY, mainTable.getSelectedEntries()); + int entriesDeleted = + doDeleteEntry(StandardActions.DELETE_ENTRY, mainTable.getSelectedEntries()); dialogService.notify(Localization.lang("Deleted %0 entry(ies)", entriesDeleted)); } @@ -998,21 +1096,41 @@ 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(); + List linkedFileList = + entries.stream() + .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()); - - new DeleteFileAction(dialogService, preferences.getFilePreferences(), bibDatabaseContext, viewModels).execute(); + List viewModels = + linkedFileList.stream() + .map( + linkedFile -> + LinkedFileViewModel.fromLinkedFile( + linkedFile, + null, + bibDatabaseContext, + null, + null, + preferences)) + .collect(Collectors.toList()); + + new DeleteFileAction( + dialogService, + preferences.getFilePreferences(), + bibDatabaseContext, + viewModels) + .execute(); } } @@ -1049,52 +1167,56 @@ public void resetChangedProperties() { * @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, - 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, + 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, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, - taskExecutor, - true); + LibraryTab newTab = + new LibraryTab( + context, + tabContainer, + dialogService, + preferences, + stateManager, + fileUpdateMonitor, + entryTypesManager, + undoManager, + clipBoardManager, + taskExecutor, + true); newTab.setDataLoadingTask(dataLoadingTask); - dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted) - .onSuccess(newTab::onDatabaseLoadingSucceed) - .onFailure(newTab::onDatabaseLoadingFailed) - .executeWith(taskExecutor); + dataLoadingTask + .onRunning(newTab::onDatabaseLoadingStarted) + .onSuccess(newTab::onDatabaseLoadingSucceed) + .onFailure(newTab::onDatabaseLoadingFailed) + .executeWith(taskExecutor); return newTab; } - public static LibraryTab createLibraryTab(BibDatabaseContext databaseContext, - LibraryTabContainer tabContainer, - DialogService dialogService, - GuiPreferences preferences, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public static LibraryTab createLibraryTab( + BibDatabaseContext databaseContext, + LibraryTabContainer tabContainer, + DialogService dialogService, + GuiPreferences preferences, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + UndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { Objects.requireNonNull(databaseContext); return new LibraryTab( @@ -1122,8 +1244,12 @@ 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())); } } } @@ -1150,7 +1276,11 @@ public void listen(EntriesRemovedEvent removedEntriesEvent) { @Subscribe public void listen(FieldChangedEvent fieldChangedEvent) { - luceneManager.updateEntry(fieldChangedEvent.getBibEntry(), fieldChangedEvent.getOldValue(), fieldChangedEvent.getNewValue(), fieldChangedEvent.getField().equals(StandardField.FILE)); + luceneManager.updateEntry( + fieldChangedEvent.getBibEntry(), + fieldChangedEvent.getOldValue(), + fieldChangedEvent.getNewValue(), + fieldChangedEvent.getField().equals(StandardField.FILE)); } } @@ -1178,10 +1308,12 @@ public DatabaseNotification getNotificationPane() { @Override public String toString() { - return "LibraryTab{" + - "bibDatabaseContext=" + bibDatabaseContext + - ", showing=" + showing + - '}'; + return "LibraryTab{" + + "bibDatabaseContext=" + + bibDatabaseContext + + ", showing=" + + showing + + '}'; } public LibraryTabContainer getLibraryTabContainer() { diff --git a/src/main/java/org/jabref/gui/LibraryTabContainer.java b/src/main/java/org/jabref/gui/LibraryTabContainer.java index 139653c87caf..1ad7390ade1f 100644 --- a/src/main/java/org/jabref/gui/LibraryTabContainer.java +++ b/src/main/java/org/jabref/gui/LibraryTabContainer.java @@ -1,19 +1,17 @@ package org.jabref.gui; -import java.util.List; - import org.jabref.model.database.BibDatabaseContext; - import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.List; + @NullMarked public interface LibraryTabContainer { List getLibraryTabs(); - @Nullable - LibraryTab getCurrentLibraryTab(); + @Nullable LibraryTab getCurrentLibraryTab(); void showLibraryTab(LibraryTab libraryTab); diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index e75ffcdc89e9..77cb3e5f41a4 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -1,9 +1,7 @@ package org.jabref.gui; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; import javafx.beans.Observable; import javafx.beans.binding.Bindings; @@ -33,12 +31,14 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.search.SearchQuery; - -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.EasyBinding; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + /** * This class manages the GUI-state of JabRef, including: * @@ -56,26 +56,60 @@ public class StateManager { private static final Logger LOGGER = LoggerFactory.getLogger(StateManager.class); private final CustomLocalDragboard localDragboard = new CustomLocalDragboard(); - private final ObservableList openDatabases = FXCollections.observableArrayList(); - private final OptionalObjectProperty activeDatabase = OptionalObjectProperty.empty(); + 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 luceneManagers = FXCollections.observableHashMap(); - private final OptionalObjectProperty activeSearchQuery = OptionalObjectProperty.empty(); - private final OptionalObjectProperty activeGlobalSearchQuery = OptionalObjectProperty.empty(); + private final ObservableMap> selectedGroups = + FXCollections.observableHashMap(); + private final ObservableMap luceneManagers = + FXCollections.observableHashMap(); + private final OptionalObjectProperty activeSearchQuery = + OptionalObjectProperty.empty(); + private final OptionalObjectProperty activeGlobalSearchQuery = + OptionalObjectProperty.empty(); 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> 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 ObservableMap dialogWindowStates = FXCollections.observableHashMap(); - private final ObservableList visibleSidePanes = FXCollections.observableArrayList(); - private final ObjectProperty lastAutomaticFieldEditorEdit = new SimpleObjectProperty<>(); + 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 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<>(); @@ -115,17 +149,23 @@ public void setSelectedEntries(List newSelectedEntries) { selectedEntries.setAll(newSelectedEntries); } - public void setSelectedGroups(BibDatabaseContext context, List newSelectedGroups) { + 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); } public ObservableList getSelectedGroups(BibDatabaseContext context) { - return selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()); + return selectedGroups.computeIfAbsent( + context.getUid(), k -> FXCollections.observableArrayList()); } public void clearSelectedGroups(BibDatabaseContext context) { - selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).clear(); + selectedGroups + .computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()) + .clear(); } public void setLuceneManager(BibDatabaseContext database, LuceneManager luceneManager) { @@ -193,17 +233,20 @@ public ObjectProperty lastAutomaticFieldEditorEdit return lastAutomaticFieldEditorEdit; } - public void setLastAutomaticFieldEditorEdit(LastAutomaticFieldEditorEdit automaticFieldEditorEdit) { + public void setLastAutomaticFieldEditorEdit( + LastAutomaticFieldEditorEdit automaticFieldEditorEdit) { lastAutomaticFieldEditorEditProperty().set(automaticFieldEditorEdit); } public List collectAllDatabasePaths() { 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; } diff --git a/src/main/java/org/jabref/gui/UpdateTimestampListener.java b/src/main/java/org/jabref/gui/UpdateTimestampListener.java index b975773dee80..2fcd52151259 100644 --- a/src/main/java/org/jabref/gui/UpdateTimestampListener.java +++ b/src/main/java/org/jabref/gui/UpdateTimestampListener.java @@ -1,12 +1,12 @@ package org.jabref.gui; +import com.google.common.eventbus.Subscribe; + import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.entry.event.EntryChangedEvent; import org.jabref.model.entry.field.StandardField; -import com.google.common.eventbus.Subscribe; - /** * Updates the timestamp of changed entries if the feature is enabled */ @@ -19,11 +19,16 @@ 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 behaviour, - // 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 behaviour, + // 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/src/main/java/org/jabref/gui/WorkspacePreferences.java b/src/main/java/org/jabref/gui/WorkspacePreferences.java index 2948be7582e7..fe6c6cea8a83 100644 --- a/src/main/java/org/jabref/gui/WorkspacePreferences.java +++ b/src/main/java/org/jabref/gui/WorkspacePreferences.java @@ -1,7 +1,5 @@ package org.jabref.gui; -import java.util.List; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; @@ -14,6 +12,8 @@ import org.jabref.gui.theme.Theme; import org.jabref.logic.l10n.Language; +import java.util.List; + public class WorkspacePreferences { private final ObjectProperty language; private final BooleanProperty shouldOverrideDefaultFontSize; @@ -27,26 +27,29 @@ public class WorkspacePreferences { private final BooleanProperty confirmDelete; 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, - List selectedSlrCatalogs) { + public WorkspacePreferences( + Language language, + boolean shouldOverrideDefaultFontSize, + int mainFontSize, + int defaultFontSize, + Theme theme, + boolean themeSyncOs, + boolean shouldOpenLastEdited, + boolean showAdvancedHints, + boolean warnAboutDuplicatesInInspection, + boolean confirmDelete, + List selectedSlrCatalogs) { this.language = new SimpleObjectProperty<>(language); - this.shouldOverrideDefaultFontSize = new SimpleBooleanProperty(shouldOverrideDefaultFontSize); + this.shouldOverrideDefaultFontSize = + new SimpleBooleanProperty(shouldOverrideDefaultFontSize); this.mainFontSize = new SimpleIntegerProperty(mainFontSize); this.defaultFontSize = new SimpleIntegerProperty(defaultFontSize); this.theme = new SimpleObjectProperty<>(theme); this.themeSyncOs = new SimpleBooleanProperty(themeSyncOs); this.shouldOpenLastEdited = new SimpleBooleanProperty(shouldOpenLastEdited); this.showAdvancedHints = new SimpleBooleanProperty(showAdvancedHints); - this.warnAboutDuplicatesInInspection = new SimpleBooleanProperty(warnAboutDuplicatesInInspection); + this.warnAboutDuplicatesInInspection = + new SimpleBooleanProperty(warnAboutDuplicatesInInspection); this.confirmDelete = new SimpleBooleanProperty(confirmDelete); this.selectedSlrCatalogs = FXCollections.observableArrayList(selectedSlrCatalogs); } diff --git a/src/main/java/org/jabref/gui/actions/Action.java b/src/main/java/org/jabref/gui/actions/Action.java index e6ccfe487b24..d6ee0ebf9679 100644 --- a/src/main/java/org/jabref/gui/actions/Action.java +++ b/src/main/java/org/jabref/gui/actions/Action.java @@ -1,10 +1,10 @@ package org.jabref.gui.actions; -import java.util.Optional; - import org.jabref.gui.icon.JabRefIcon; import org.jabref.gui.keyboard.KeyBinding; +import java.util.Optional; + public interface Action { default Optional getIcon() { return Optional.empty(); diff --git a/src/main/java/org/jabref/gui/actions/ActionFactory.java b/src/main/java/org/jabref/gui/actions/ActionFactory.java index 74f1b25e7401..5f188369d95c 100644 --- a/src/main/java/org/jabref/gui/actions/ActionFactory.java +++ b/src/main/java/org/jabref/gui/actions/ActionFactory.java @@ -1,8 +1,10 @@ package org.jabref.gui.actions; -import java.lang.reflect.InaccessibleObjectException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import com.airhacks.afterburner.injection.Injector; +import com.sun.javafx.scene.control.ContextMenuContent; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.commands.Command; import javafx.beans.binding.BooleanExpression; import javafx.scene.control.Button; @@ -13,17 +15,16 @@ import javafx.scene.control.MenuItem; import javafx.scene.control.Tooltip; +import org.controlsfx.control.action.ActionUtils; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.model.strings.StringUtil; - -import com.airhacks.afterburner.injection.Injector; -import com.sun.javafx.scene.control.ContextMenuContent; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.commands.Command; -import org.controlsfx.control.action.ActionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.InaccessibleObjectException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + /** * Helper class to create and style controls according to an {@link Action}. */ @@ -58,17 +59,22 @@ private static void setGraphic(MenuItem node, Action action) { * 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 { // We have to use reflection to get the associated label try { - Method getLabel = ContextMenuContent.MenuItemContainer.class.getDeclaredMethod("getLabel"); + 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); } } @@ -76,7 +82,8 @@ private static Label getAssociatedNode(MenuItem menuItem) { } public MenuItem configureMenuItem(Action action, Command command, MenuItem menuItem) { - ActionUtils.configureMenuItem(new JabRefAction(action, command, keyBindingRepository), menuItem); + ActionUtils.configureMenuItem( + new JabRefAction(action, command, keyBindingRepository), menuItem); setGraphic(menuItem, action); enableTooltips(command, menuItem); return menuItem; @@ -96,8 +103,7 @@ private static void enableTooltips(Command command, MenuItem menuItem) { label.setTooltip(new Tooltip(message)); } } - } - ); + }); } } @@ -108,15 +114,20 @@ public MenuItem createMenuItem(Action action, Command command) { } 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); return checkMenuItem; } - public CheckMenuItem createCheckMenuItem(Action action, Command command, BooleanExpression selectedBinding) { - CheckMenuItem checkMenuItem = ActionUtils.createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); + public CheckMenuItem createCheckMenuItem( + Action action, Command command, BooleanExpression selectedBinding) { + CheckMenuItem checkMenuItem = + ActionUtils.createCheckMenuItem( + new JabRefAction(action, command, keyBindingRepository)); EasyBind.subscribe(selectedBinding, checkMenuItem::setSelected); setGraphic(checkMenuItem, action); @@ -138,7 +149,10 @@ 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"); diff --git a/src/main/java/org/jabref/gui/actions/ActionHelper.java b/src/main/java/org/jabref/gui/actions/ActionHelper.java index 437d38d9bfe0..e3e5273f67bc 100644 --- a/src/main/java/org/jabref/gui/actions/ActionHelper.java +++ b/src/main/java/org/jabref/gui/actions/ActionHelper.java @@ -1,9 +1,7 @@ package org.jabref.gui.actions; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; import javafx.beans.binding.Binding; import javafx.beans.binding.Bindings; @@ -20,8 +18,10 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.EasyBinding; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; public class ActionHelper { @@ -30,7 +30,12 @@ public static BooleanExpression needsDatabase(StateManager stateManager) { } 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); } @@ -39,7 +44,10 @@ 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); } @@ -47,44 +55,61 @@ public static BooleanExpression needsEntriesSelected(StateManager stateManager) return Bindings.isNotEmpty(stateManager.getSelectedEntries()); } - public static BooleanExpression needsEntriesSelected(int numberOfEntries, StateManager stateManager) { - return Bindings.createBooleanBinding(() -> stateManager.getSelectedEntries().size() == numberOfEntries, - stateManager.getSelectedEntries()); + public static BooleanExpression needsEntriesSelected( + int numberOfEntries, StateManager stateManager) { + return Bindings.createBooleanBinding( + () -> stateManager.getSelectedEntries().size() == numberOfEntries, + stateManager.getSelectedEntries()); } - public static BooleanExpression isFieldSetForSelectedEntry(Field field, StateManager stateManager) { + public static BooleanExpression isFieldSetForSelectedEntry( + Field field, StateManager stateManager) { return isAnyFieldSetForSelectedEntry(Collections.singletonList(field), stateManager); } - public static BooleanExpression isAnyFieldSetForSelectedEntry(List fields, StateManager stateManager) { + public static BooleanExpression isAnyFieldSetForSelectedEntry( + List fields, StateManager stateManager) { ObservableList selectedEntries = stateManager.getSelectedEntries(); - Binding fieldsAreSet = EasyBind.valueAt(selectedEntries, 0) - .mapObservable(entry -> Bindings.createBooleanBinding(() -> { - return entry.getFields().stream().anyMatch(fields::contains); - }, entry.getFieldsObservable())) - .orElseOpt(false); + Binding fieldsAreSet = + EasyBind.valueAt(selectedEntries, 0) + .mapObservable( + entry -> + Bindings.createBooleanBinding( + () -> { + return 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(); - - if ((!entry.getFiles().isEmpty()) && stateManager.getActiveDatabase().isPresent()) { - if (files.getFirst().isOnlineLink()) { - return true; - } - - Optional filename = FileUtil.find( - stateManager.getActiveDatabase().get(), - files.getFirst().getLink(), - preferences.getFilePreferences()); - return filename.isPresent(); - } else { - return false; - } - }).orElseOpt(false); + Binding fileIsPresent = + EasyBind.valueAt(selectedEntries, 0) + .mapOpt( + entry -> { + List files = entry.getFiles(); + + if ((!entry.getFiles().isEmpty()) + && stateManager.getActiveDatabase().isPresent()) { + if (files.getFirst().isOnlineLink()) { + return true; + } + + Optional filename = + FileUtil.find( + stateManager.getActiveDatabase().get(), + files.getFirst().getLink(), + preferences.getFilePreferences()); + return filename.isPresent(); + } else { + return false; + } + }) + .orElseOpt(false); return BooleanExpression.booleanExpression(fileIsPresent); } @@ -98,7 +123,9 @@ public static BooleanExpression isFilePresentForSelectedEntry(StateManager state * @return a boolean binding */ public static BooleanExpression hasLinkedFileForSelectedEntries(StateManager stateManager) { - return BooleanExpression.booleanExpression(EasyBind.reduce(stateManager.getSelectedEntries(), - entries -> entries.anyMatch(entry -> !entry.getFiles().isEmpty()))); + return BooleanExpression.booleanExpression( + EasyBind.reduce( + stateManager.getSelectedEntries(), + entries -> entries.anyMatch(entry -> !entry.getFiles().isEmpty()))); } } diff --git a/src/main/java/org/jabref/gui/actions/JabRefAction.java b/src/main/java/org/jabref/gui/actions/JabRefAction.java index 50d6be4d3b26..a7cc342c8ff8 100644 --- a/src/main/java/org/jabref/gui/actions/JabRefAction.java +++ b/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -1,11 +1,11 @@ package org.jabref.gui.actions; +import de.saxsys.mvvmfx.utils.commands.Command; + import javafx.beans.binding.Bindings; import org.jabref.gui.keyboard.KeyBindingRepository; -import de.saxsys.mvvmfx.utils.commands.Command; - /** * Wrapper around one of our actions from {@link Action} to convert them to controlsfx {@link org.controlsfx.control.action.Action}. */ @@ -14,7 +14,9 @@ class JabRefAction extends org.controlsfx.control.action.Action { public JabRefAction(Action action, KeyBindingRepository keyBindingRepository) { super(action.getText()); action.getIcon().ifPresent(icon -> setGraphic(icon.getGraphicNode())); - action.getKeyBinding().flatMap(keyBindingRepository::getKeyCombination).ifPresent(this::setAccelerator); + action.getKeyBinding() + .flatMap(keyBindingRepository::getKeyCombination) + .ifPresent(this::setAccelerator); setLongText(action.getDescription()); } @@ -27,7 +29,11 @@ public JabRefAction(Action action, Command command, KeyBindingRepository keyBind disabledProperty().bind(command.executableProperty().not()); if (command instanceof SimpleCommand simpleCommand) { - longTextProperty().bind(Bindings.concat(action.getDescription(), simpleCommand.statusMessageProperty())); + longTextProperty() + .bind( + Bindings.concat( + action.getDescription(), + simpleCommand.statusMessageProperty())); } } } diff --git a/src/main/java/org/jabref/gui/actions/SimpleCommand.java b/src/main/java/org/jabref/gui/actions/SimpleCommand.java index 89ef7ad100a6..ad6edc420214 100644 --- a/src/main/java/org/jabref/gui/actions/SimpleCommand.java +++ b/src/main/java/org/jabref/gui/actions/SimpleCommand.java @@ -1,13 +1,13 @@ package org.jabref.gui.actions; +import de.saxsys.mvvmfx.utils.commands.CommandBase; + import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; import org.jabref.gui.util.BindingsHelper; -import de.saxsys.mvvmfx.utils.commands.CommandBase; - /** * A simple command that does not track progress of the action. */ diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 7f44fb8810f6..67d102b908b9 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -1,51 +1,74 @@ package org.jabref.gui.actions; -import java.util.Objects; -import java.util.Optional; - import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.l10n.Localization; -public enum StandardActions implements Action { +import java.util.Objects; +import java.util.Optional; +public enum StandardActions implements Action { 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_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_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), COPY_CITATION_TEXT(Localization.lang("Copy citation (text)")), 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_TO_CLIPBOARD( + Localization.lang("Export to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), + EXPORT_SELECTED_TO_CLIPBOARD( + Localization.lang("Export selected entries to clipboard"), + IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), COPY(Localization.lang("Copy"), IconTheme.JabRefIcons.COPY, KeyBinding.COPY), PASTE(Localization.lang("Paste"), IconTheme.JabRefIcons.PASTE, KeyBinding.PASTE), CUT(Localization.lang("Cut"), IconTheme.JabRefIcons.CUT, KeyBinding.CUT), DELETE(Localization.lang("Delete"), IconTheme.JabRefIcons.DELETE_ENTRY), - DELETE_ENTRY(Localization.lang("Delete entry"), IconTheme.JabRefIcons.DELETE_ENTRY, KeyBinding.DELETE_ENTRY), + 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_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), - OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI), + 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), + 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/...")), ATTACH_FILE(Localization.lang("Attach file"), IconTheme.JabRefIcons.ATTACH_FILE), - ATTACH_FILE_FROM_URL(Localization.lang("Attach file from URL"), IconTheme.JabRefIcons.DOWNLOAD_FILE), + ATTACH_FILE_FROM_URL( + Localization.lang("Attach file from URL"), IconTheme.JabRefIcons.DOWNLOAD_FILE), PRIORITY(Localization.lang("Priority"), IconTheme.JabRefIcons.PRIORITY), CLEAR_PRIORITY(Localization.lang("Clear priority")), PRIORITY_HIGH(Localization.lang("Set priority to high"), IconTheme.JabRefIcons.PRIORITY_HIGH), - PRIORITY_MEDIUM(Localization.lang("Set priority to medium"), IconTheme.JabRefIcons.PRIORITY_MEDIUM), + PRIORITY_MEDIUM( + Localization.lang("Set priority to medium"), IconTheme.JabRefIcons.PRIORITY_MEDIUM), PRIORITY_LOW(Localization.lang("Set priority to low"), IconTheme.JabRefIcons.PRIORITY_LOW), QUALITY(Localization.lang("Quality"), IconTheme.JabRefIcons.QUALITY), - QUALITY_ASSURED(Localization.lang("Toggle quality assured"), IconTheme.JabRefIcons.QUALITY_ASSURED), + QUALITY_ASSURED( + Localization.lang("Toggle quality assured"), IconTheme.JabRefIcons.QUALITY_ASSURED), RANKING(Localization.lang("Rank"), IconTheme.JabRefIcons.RANKING), CLEAR_RANK(Localization.lang("Clear rank")), RANK_1(Localization.lang("Set rank to one"), IconTheme.JabRefIcons.RANK1), @@ -57,29 +80,65 @@ public enum StandardActions implements Action { TOGGLE_PRINTED(Localization.lang("Toggle print status"), IconTheme.JabRefIcons.PRINTED), READ_STATUS(Localization.lang("Read status"), IconTheme.JabRefIcons.READ_STATUS), CLEAR_READ_STATUS(Localization.lang("Clear read status"), 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), + 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), RELEVANCE(Localization.lang("Relevance"), IconTheme.JabRefIcons.RELEVANCE), RELEVANT(Localization.lang("Toggle relevance"), IconTheme.JabRefIcons.RELEVANCE), NEW_LIBRARY(Localization.lang("New library"), IconTheme.JabRefIcons.NEW), - OPEN_LIBRARY(Localization.lang("Open library"), IconTheme.JabRefIcons.OPEN, KeyBinding.OPEN_DATABASE), + OPEN_LIBRARY( + Localization.lang("Open library"), + IconTheme.JabRefIcons.OPEN, + KeyBinding.OPEN_DATABASE), IMPORT(Localization.lang("Import"), IconTheme.JabRefIcons.IMPORT), EXPORT(Localization.lang("Export"), IconTheme.JabRefIcons.EXPORT, KeyBinding.EXPORT), - SAVE_LIBRARY(Localization.lang("Save library"), IconTheme.JabRefIcons.SAVE, KeyBinding.SAVE_DATABASE), + SAVE_LIBRARY( + Localization.lang("Save library"), + IconTheme.JabRefIcons.SAVE, + KeyBinding.SAVE_DATABASE), SAVE_LIBRARY_AS(Localization.lang("Save library as..."), KeyBinding.SAVE_DATABASE_AS), SAVE_SELECTED_AS_PLAIN_BIBTEX(Localization.lang("Save selected as plain BibTeX...")), - SAVE_ALL(Localization.lang("Save all"), Localization.lang("Save all open libraries"), IconTheme.JabRefIcons.SAVE_ALL, KeyBinding.SAVE_ALL), - IMPORT_INTO_NEW_LIBRARY(Localization.lang("Import into new library"), KeyBinding.IMPORT_INTO_NEW_DATABASE), - IMPORT_INTO_CURRENT_LIBRARY(Localization.lang("Import into current library"), KeyBinding.IMPORT_INTO_CURRENT_DATABASE), + SAVE_ALL( + Localization.lang("Save all"), + Localization.lang("Save all open libraries"), + IconTheme.JabRefIcons.SAVE_ALL, + KeyBinding.SAVE_ALL), + IMPORT_INTO_NEW_LIBRARY( + Localization.lang("Import into new library"), KeyBinding.IMPORT_INTO_NEW_DATABASE), + IMPORT_INTO_CURRENT_LIBRARY( + Localization.lang("Import into current library"), + KeyBinding.IMPORT_INTO_CURRENT_DATABASE), EXPORT_ALL(Localization.lang("Export all entries")), 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), + 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), 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), @@ -87,32 +146,85 @@ public enum StandardActions implements Action { MASS_SET_FIELDS(Localization.lang("Manage field names & content")), 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_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), - 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), + 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), 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), + 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)")), - UNABBREVIATE(Localization.lang("Unabbreviate journal names"), Localization.lang("Unabbreviate journal names of the selected entries"), KeyBinding.UNABBREVIATE), + 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)")), + 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")), @@ -120,68 +232,167 @@ public enum StandardActions implements Action { SETUP_GENERAL_FIELDS(Localization.lang("Set up general fields")), MANAGE_PROTECTED_TERMS(Localization.lang("Manage protected terms")), CITATION_KEY_PATTERN(Localization.lang("Citation key patterns")), - SHOW_PREFS(Localization.lang("Preferences"), IconTheme.JabRefIcons.PREFERENCES, KeyBinding.SHOW_PREFS), + 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), + 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), 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), + 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")), EXPAND_ALL(Localization.lang("Expand all")), COLLAPSE_ALL(Localization.lang("Collapse all")), - NEW_ENTRY(Localization.lang("New entry"), IconTheme.JabRefIcons.ADD_ENTRY, KeyBinding.NEW_ENTRY), + NEW_ENTRY( + Localization.lang("New entry"), IconTheme.JabRefIcons.ADD_ENTRY, KeyBinding.NEW_ENTRY), NEW_ARTICLE(Localization.lang("New article"), IconTheme.JabRefIcons.ADD_ARTICLE), - NEW_ENTRY_FROM_PLAIN_TEXT(Localization.lang("New entry from plain text"), IconTheme.JabRefIcons.NEW_ENTRY_FROM_PLAIN_TEXT), - NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE(Localization.lang("New entry from plain text (online)"), IconTheme.JabRefIcons.NEW_ENTRY_FROM_PLAIN_TEXT, KeyBinding.NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE), - NEW_ENTRY_FROM_PLAIN_TEXT_OFFLINE(Localization.lang("New entry from plain text (offline)"), IconTheme.JabRefIcons.NEW_ENTRY_FROM_PLAIN_TEXT), + NEW_ENTRY_FROM_PLAIN_TEXT( + Localization.lang("New entry from plain text"), + IconTheme.JabRefIcons.NEW_ENTRY_FROM_PLAIN_TEXT), + NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE( + Localization.lang("New entry from plain text (online)"), + IconTheme.JabRefIcons.NEW_ENTRY_FROM_PLAIN_TEXT, + KeyBinding.NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE), + NEW_ENTRY_FROM_PLAIN_TEXT_OFFLINE( + Localization.lang("New entry from plain text (offline)"), + IconTheme.JabRefIcons.NEW_ENTRY_FROM_PLAIN_TEXT), 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), + 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), CHECK_INTEGRITY(Localization.lang("Check integrity"), KeyBinding.CHECK_INTEGRITY), - FIND_UNLINKED_FILES(Localization.lang("Search for unlinked local files"), IconTheme.JabRefIcons.SEARCH, KeyBinding.FIND_UNLINKED_FILES), - AUTO_LINK_FILES(Localization.lang("Automatically set file links"), IconTheme.JabRefIcons.AUTO_FILE_LINK, KeyBinding.AUTOMATICALLY_LINK_FILES), + FIND_UNLINKED_FILES( + Localization.lang("Search for unlinked local files"), + IconTheme.JabRefIcons.SEARCH, + KeyBinding.FIND_UNLINKED_FILES), + AUTO_LINK_FILES( + Localization.lang("Automatically set file links"), + IconTheme.JabRefIcons.AUTO_FILE_LINK, + KeyBinding.AUTOMATICALLY_LINK_FILES), LOOKUP_DOC_IDENTIFIER(Localization.lang("Search document identifier online")), - LOOKUP_FULLTEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, KeyBinding.DOWNLOAD_FULL_TEXT), - GENERATE_CITE_KEY(Localization.lang("Generate 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("Cleanup entries"), IconTheme.JabRefIcons.CLEANUP_ENTRIES, KeyBinding.CLEANUP), - SET_FILE_LINKS(Localization.lang("Automatically set file links"), KeyBinding.AUTOMATICALLY_LINK_FILES), + 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("Cleanup entries"), + IconTheme.JabRefIcons.CLEANUP_ENTRIES, + KeyBinding.CLEANUP), + SET_FILE_LINKS( + Localization.lang("Automatically set file links"), KeyBinding.AUTOMATICALLY_LINK_FILES), - EDIT_FILE_LINK(Localization.lang("Edit"), IconTheme.JabRefIcons.EDIT, KeyBinding.OPEN_CLOSE_ENTRY_EDITOR), + EDIT_FILE_LINK( + Localization.lang("Edit"), + IconTheme.JabRefIcons.EDIT, + KeyBinding.OPEN_CLOSE_ENTRY_EDITOR), 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 file to a given name"), 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), + RENAME_FILE_TO_PATTERN( + Localization.lang("Rename file to defined pattern"), IconTheme.JabRefIcons.AUTO_RENAME), + RENAME_FILE_TO_NAME( + Localization.lang("Rename file to a given name"), + 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), REMOVE_LINK(Localization.lang("Remove link"), 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_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_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_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), 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_TWITTER("Twitter", Localization.lang("Opens JabRef's Twitter page"), IconTheme.JabRefIcons.TWITTER), - OPEN_BLOG(Localization.lang("Blog"), Localization.lang("Opens JabRef's blog"), IconTheme.JabRefIcons.BLOG), - OPEN_DEV_VERSION_LINK(Localization.lang("Development version"), Localization.lang("Opens a link where the current development version can be downloaded")), - OPEN_CHANGELOG(Localization.lang("View change log"), Localization.lang("See what has been changed in the JabRef versions")), - 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("Online help forum"), Localization.lang("Online help forum"), IconTheme.JabRefIcons.FORUM), - ERROR_CONSOLE(Localization.lang("View event log"), Localization.lang("Display all error messages")), + 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_TWITTER( + "Twitter", + Localization.lang("Opens JabRef's Twitter page"), + IconTheme.JabRefIcons.TWITTER), + OPEN_BLOG( + Localization.lang("Blog"), + Localization.lang("Opens JabRef's blog"), + IconTheme.JabRefIcons.BLOG), + OPEN_DEV_VERSION_LINK( + Localization.lang("Development version"), + Localization.lang( + "Opens a link where the current development version can be downloaded")), + OPEN_CHANGELOG( + Localization.lang("View change log"), + Localization.lang("See what has been changed in the JabRef versions")), + 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("Online help forum"), + Localization.lang("Online help forum"), + IconTheme.JabRefIcons.FORUM), + ERROR_CONSOLE( + Localization.lang("View event log"), Localization.lang("Display all error messages")), SEARCH_FOR_UPDATES(Localization.lang("Check for updates")), ABOUT(Localization.lang("About JabRef"), Localization.lang("About JabRef")), @@ -200,7 +411,8 @@ public enum StandardActions implements Action { 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)")), - GROUP_SUBGROUP_SORT_ENTRIES_REVERSE(Localization.lang("Sort subgroups by # of entries (Ascending)")), + GROUP_SUBGROUP_SORT_ENTRIES_REVERSE( + Localization.lang("Sort subgroups by # of entries (Ascending)")), GROUP_ENTRIES_ADD(Localization.lang("Add selected entries to this group")), GROUP_ENTRIES_REMOVE(Localization.lang("Remove selected entries from this group")), @@ -236,7 +448,8 @@ public enum StandardActions implements Action { this.keyBinding = Optional.empty(); } - StandardActions(String text, String description, IconTheme.JabRefIcons icon, KeyBinding keyBinding) { + StandardActions( + String text, String description, IconTheme.JabRefIcons icon, KeyBinding keyBinding) { this.text = text; this.description = description; this.icon = Optional.of(icon); diff --git a/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java b/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java index 91b3e926269b..0125ddc2b0cd 100644 --- a/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java +++ b/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java @@ -1,6 +1,6 @@ package org.jabref.gui.ai; -import java.util.List; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -11,7 +11,7 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.LinkedFile; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.util.List; public class ClearEmbeddingsAction extends SimpleCommand { private final StateManager stateManager; @@ -19,10 +19,11 @@ public class ClearEmbeddingsAction extends SimpleCommand { 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,9 +37,10 @@ public void execute() { return; } - boolean confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Clear embeddings cache"), - Localization.lang("Clear embeddings cache for current library?")); + boolean confirmed = + dialogService.showConfirmationDialogAndWait( + Localization.lang("Clear embeddings cache"), + Localization.lang("Clear embeddings cache for current library?")); if (!confirmed) { return; @@ -46,16 +48,12 @@ 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/src/main/java/org/jabref/gui/ai/chatting/chathistory/ChatHistoryService.java b/src/main/java/org/jabref/gui/ai/chatting/chathistory/ChatHistoryService.java index 872a263c7a1a..22729b2abfbd 100644 --- a/src/main/java/org/jabref/gui/ai/chatting/chathistory/ChatHistoryService.java +++ b/src/main/java/org/jabref/gui/ai/chatting/chathistory/ChatHistoryService.java @@ -1,10 +1,9 @@ package org.jabref.gui.ai.chatting.chathistory; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.TreeMap; +import com.airhacks.afterburner.injection.Injector; +import com.google.common.eventbus.Subscribe; + +import dev.langchain4j.data.message.ChatMessage; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -24,13 +23,15 @@ import org.jabref.model.entry.field.InternalField; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.groups.GroupTreeNode; - -import com.airhacks.afterburner.injection.Injector; -import com.google.common.eventbus.Subscribe; -import dev.langchain4j.data.message.ChatMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.TreeMap; + /** * Main class for getting and storing chat history for entries and groups. * Use this class in logic and UI. @@ -58,35 +59,50 @@ public class ChatHistoryService implements AutoCloseable { private static final String CHAT_HISTORY_FILE_NAME = "chat-histories.mv"; - private final StateManager stateManager = Injector.instantiateModelOrService(StateManager.class); + private final StateManager stateManager = + Injector.instantiateModelOrService(StateManager.class); private final CitationKeyPatternPreferences citationKeyPatternPreferences; private final ChatHistoryStorage implementation; - private record ChatHistoryManagementRecord(Optional bibDatabaseContext, ObservableList chatHistory) { } + private record ChatHistoryManagementRecord( + Optional bibDatabaseContext, + ObservableList chatHistory) {} // We use a {@link TreeMap} here to store {@link BibEntry} chat histories by their id. // When you compare {@link BibEntry} instances, they are compared by value, not by reference. - // And when you store {@link BibEntry} instances in a {@link HashMap}, an old hash may be stored when the {@link BibEntry} is changed. + // And when you store {@link BibEntry} instances in a {@link HashMap}, an old hash may be stored + // when the {@link BibEntry} is changed. // See also ADR-38. - private final TreeMap bibEntriesChatHistory = new TreeMap<>(Comparator.comparing(BibEntry::getId)); + private final TreeMap bibEntriesChatHistory = + new TreeMap<>(Comparator.comparing(BibEntry::getId)); // We use {@link TreeMap} for group chat history for the same reason as for {@link BibEntry}ies. - private final TreeMap groupsChatHistory = new TreeMap<>((o1, o2) -> { - // The most important thing is to catch equality/non-equality. - // For "less" or "bigger" comparison, we will fall back to group names. - return o1 == o2 ? 0 : o1.getGroup().getName().compareTo(o2.getGroup().getName()); - }); - - public ChatHistoryService(CitationKeyPatternPreferences citationKeyPatternPreferences, NotificationService notificationService) { + private final TreeMap groupsChatHistory = + new TreeMap<>( + (o1, o2) -> { + // The most important thing is to catch equality/non-equality. + // For "less" or "bigger" comparison, we will fall back to group names. + return o1 == o2 + ? 0 + : o1.getGroup().getName().compareTo(o2.getGroup().getName()); + }); + + public ChatHistoryService( + CitationKeyPatternPreferences citationKeyPatternPreferences, + NotificationService notificationService) { this.citationKeyPatternPreferences = citationKeyPatternPreferences; - this.implementation = new MVStoreChatHistoryStorage(Directories.getAiFilesDirectory().resolve(CHAT_HISTORY_FILE_NAME), notificationService); + this.implementation = + new MVStoreChatHistoryStorage( + Directories.getAiFilesDirectory().resolve(CHAT_HISTORY_FILE_NAME), + notificationService); configureHistoryTransfer(); } - public ChatHistoryService(CitationKeyPatternPreferences citationKeyPatternPreferences, - ChatHistoryStorage implementation) { + public ChatHistoryService( + CitationKeyPatternPreferences citationKeyPatternPreferences, + ChatHistoryStorage implementation) { this.citationKeyPatternPreferences = citationKeyPatternPreferences; this.implementation = implementation; @@ -94,52 +110,98 @@ public ChatHistoryService(CitationKeyPatternPreferences citationKeyPatternPrefer } private void configureHistoryTransfer() { - stateManager.getOpenDatabases().addListener((ListChangeListener) change -> { - while (change.next()) { - if (change.wasAdded()) { - change.getAddedSubList().forEach(this::configureHistoryTransfer); - } - } - }); + stateManager + .getOpenDatabases() + .addListener( + (ListChangeListener) + change -> { + while (change.next()) { + if (change.wasAdded()) { + change.getAddedSubList() + .forEach(this::configureHistoryTransfer); + } + } + }); } private void configureHistoryTransfer(BibDatabaseContext bibDatabaseContext) { - bibDatabaseContext.getMetaData().getGroups().ifPresent(rootGroupTreeNode -> { - rootGroupTreeNode.iterateOverTree().forEach(groupNode -> { - groupNode.getGroup().nameProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != null && oldValue != null) { - transferGroupHistory(bibDatabaseContext, groupNode, oldValue, newValue); - } - }); - - groupNode.getGroupProperty().addListener((obs, oldValue, newValue) -> { - if (oldValue != null && newValue != null) { - transferGroupHistory(bibDatabaseContext, groupNode, oldValue.getName(), newValue.getName()); - } - }); - }); - }); - - bibDatabaseContext.getDatabase().getEntries().forEach(entry -> { - entry.registerListener(new CitationKeyChangeListener(bibDatabaseContext)); - }); + bibDatabaseContext + .getMetaData() + .getGroups() + .ifPresent( + rootGroupTreeNode -> { + rootGroupTreeNode + .iterateOverTree() + .forEach( + groupNode -> { + groupNode + .getGroup() + .nameProperty() + .addListener( + (observable, + oldValue, + newValue) -> { + if (newValue != null + && oldValue != null) { + transferGroupHistory( + bibDatabaseContext, + groupNode, + oldValue, + newValue); + } + }); + + groupNode + .getGroupProperty() + .addListener( + (obs, oldValue, newValue) -> { + if (oldValue != null + && newValue != null) { + transferGroupHistory( + bibDatabaseContext, + groupNode, + oldValue.getName(), + newValue.getName()); + } + }); + }); + }); + + bibDatabaseContext + .getDatabase() + .getEntries() + .forEach( + entry -> { + entry.registerListener( + new CitationKeyChangeListener(bibDatabaseContext)); + }); } public ObservableList getChatHistoryForEntry(BibEntry entry) { - return bibEntriesChatHistory.computeIfAbsent(entry, entryArg -> { - Optional bibDatabaseContext = findBibDatabaseForEntry(entry); - - ObservableList chatHistory; - - if (bibDatabaseContext.isEmpty() || entry.getCitationKey().isEmpty() || !correctCitationKey(bibDatabaseContext.get(), entry) || bibDatabaseContext.get().getDatabasePath().isEmpty()) { - chatHistory = FXCollections.observableArrayList(); - } else { - List chatMessagesList = implementation.loadMessagesForEntry(bibDatabaseContext.get().getDatabasePath().get(), entry.getCitationKey().get()); - chatHistory = FXCollections.observableArrayList(chatMessagesList); - } - - return new ChatHistoryManagementRecord(bibDatabaseContext, chatHistory); - }).chatHistory; + return bibEntriesChatHistory.computeIfAbsent( + entry, + entryArg -> { + Optional bibDatabaseContext = + findBibDatabaseForEntry(entry); + + ObservableList chatHistory; + + if (bibDatabaseContext.isEmpty() + || entry.getCitationKey().isEmpty() + || !correctCitationKey(bibDatabaseContext.get(), entry) + || bibDatabaseContext.get().getDatabasePath().isEmpty()) { + chatHistory = FXCollections.observableArrayList(); + } else { + List chatMessagesList = + implementation.loadMessagesForEntry( + bibDatabaseContext.get().getDatabasePath().get(), + entry.getCitationKey().get()); + chatHistory = FXCollections.observableArrayList(chatMessagesList); + } + + return new ChatHistoryManagementRecord(bibDatabaseContext, chatHistory); + }) + .chatHistory; } /** @@ -157,16 +219,20 @@ public void closeChatHistoryForEntry(BibEntry entry) { return; } - Optional bibDatabaseContext = chatHistoryManagementRecord.bibDatabaseContext(); + Optional bibDatabaseContext = + chatHistoryManagementRecord.bibDatabaseContext(); - if (bibDatabaseContext.isPresent() && entry.getCitationKey().isPresent() && correctCitationKey(bibDatabaseContext.get(), entry) && bibDatabaseContext.get().getDatabasePath().isPresent()) { - // Method `correctCitationKey` will already check `entry.getCitationKey().isPresent()`, but it is still + if (bibDatabaseContext.isPresent() + && entry.getCitationKey().isPresent() + && correctCitationKey(bibDatabaseContext.get(), entry) + && bibDatabaseContext.get().getDatabasePath().isPresent()) { + // Method `correctCitationKey` will already check `entry.getCitationKey().isPresent()`, + // but it is still // there, to suppress warning from IntelliJ IDEA on `entry.getCitationKey().get()`. implementation.storeMessagesForEntry( bibDatabaseContext.get().getDatabasePath().get(), entry.getCitationKey().get(), - chatHistoryManagementRecord.chatHistory() - ); + chatHistoryManagementRecord.chatHistory()); } // TODO: What if there is two AI chats for the same entry? And one is closed and one is not? @@ -174,24 +240,29 @@ public void closeChatHistoryForEntry(BibEntry entry) { } public ObservableList getChatHistoryForGroup(GroupTreeNode group) { - return groupsChatHistory.computeIfAbsent(group, groupArg -> { - Optional bibDatabaseContext = findBibDatabaseForGroup(group); - - ObservableList chatHistory; - - if (bibDatabaseContext.isEmpty() || bibDatabaseContext.get().getDatabasePath().isEmpty()) { - chatHistory = FXCollections.observableArrayList(); - } else { - List chatMessagesList = implementation.loadMessagesForGroup( - bibDatabaseContext.get().getDatabasePath().get(), - group.getGroup().getName() - ); - - chatHistory = FXCollections.observableArrayList(chatMessagesList); - } - - return new ChatHistoryManagementRecord(bibDatabaseContext, chatHistory); - }).chatHistory; + return groupsChatHistory.computeIfAbsent( + group, + groupArg -> { + Optional bibDatabaseContext = + findBibDatabaseForGroup(group); + + ObservableList chatHistory; + + if (bibDatabaseContext.isEmpty() + || bibDatabaseContext.get().getDatabasePath().isEmpty()) { + chatHistory = FXCollections.observableArrayList(); + } else { + List chatMessagesList = + implementation.loadMessagesForGroup( + bibDatabaseContext.get().getDatabasePath().get(), + group.getGroup().getName()); + + chatHistory = FXCollections.observableArrayList(chatMessagesList); + } + + return new ChatHistoryManagementRecord(bibDatabaseContext, chatHistory); + }) + .chatHistory; } /** @@ -209,14 +280,15 @@ public void closeChatHistoryForGroup(GroupTreeNode group) { return; } - Optional bibDatabaseContext = chatHistoryManagementRecord.bibDatabaseContext(); + Optional bibDatabaseContext = + chatHistoryManagementRecord.bibDatabaseContext(); - if (bibDatabaseContext.isPresent() && bibDatabaseContext.get().getDatabasePath().isPresent()) { + if (bibDatabaseContext.isPresent() + && bibDatabaseContext.get().getDatabasePath().isPresent()) { implementation.storeMessagesForGroup( bibDatabaseContext.get().getDatabasePath().get(), group.getGroup().getName(), - chatHistoryManagementRecord.chatHistory() - ); + chatHistoryManagementRecord.chatHistory()); } // TODO: What if there is two AI chats for the same entry? And one is closed and one is not? @@ -231,33 +303,38 @@ private boolean correctCitationKey(BibDatabaseContext bibDatabaseContext, BibEnt return CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, bibEntry); } - private void tryToGenerateCitationKey(BibDatabaseContext bibDatabaseContext, BibEntry bibEntry) { - new CitationKeyGenerator(bibDatabaseContext, citationKeyPatternPreferences).generateAndSetKey(bibEntry); + private void tryToGenerateCitationKey( + BibDatabaseContext bibDatabaseContext, BibEntry bibEntry) { + new CitationKeyGenerator(bibDatabaseContext, citationKeyPatternPreferences) + .generateAndSetKey(bibEntry); } private Optional findBibDatabaseForEntry(BibEntry entry) { - return stateManager - .getOpenDatabases() - .stream() + return stateManager.getOpenDatabases().stream() .filter(dbContext -> dbContext.getDatabase().getEntries().contains(entry)) .findFirst(); } private Optional findBibDatabaseForGroup(GroupTreeNode group) { - return stateManager - .getOpenDatabases() - .stream() - .filter(dbContext -> - dbContext.getMetaData().groupsBinding().get().map(groupTreeNode -> - groupTreeNode.containsGroup(group.getGroup()) - ).orElse(false) - ) + return stateManager.getOpenDatabases().stream() + .filter( + dbContext -> + dbContext + .getMetaData() + .groupsBinding() + .get() + .map( + groupTreeNode -> + groupTreeNode.containsGroup( + group.getGroup())) + .orElse(false)) .findFirst(); } @Override public void close() { - // We need to clone `bibEntriesChatHistory.keySet()` because closeChatHistoryForEntry() modifies the `bibEntriesChatHistory` map. + // We need to clone `bibEntriesChatHistory.keySet()` because closeChatHistoryForEntry() + // modifies the `bibEntriesChatHistory` map. new HashSet<>(bibEntriesChatHistory.keySet()).forEach(this::closeChatHistoryForEntry); // Clone is for the same reason, as written above. @@ -266,30 +343,60 @@ public void close() { implementation.commit(); } - private void transferGroupHistory(BibDatabaseContext bibDatabaseContext, GroupTreeNode groupTreeNode, String oldName, String newName) { + private void transferGroupHistory( + BibDatabaseContext bibDatabaseContext, + GroupTreeNode groupTreeNode, + String oldName, + String newName) { if (bibDatabaseContext.getDatabasePath().isEmpty()) { - LOGGER.warn("Could not transfer chat history of group {} (old name: {}): database path is empty.", newName, oldName); + LOGGER.warn( + "Could not transfer chat history of group {} (old name: {}): database path is empty.", + newName, + oldName); return; } - List chatMessages = groupsChatHistory.computeIfAbsent(groupTreeNode, - e -> new ChatHistoryManagementRecord(Optional.of(bibDatabaseContext), FXCollections.observableArrayList())).chatHistory; - implementation.storeMessagesForGroup(bibDatabaseContext.getDatabasePath().get(), oldName, List.of()); - implementation.storeMessagesForGroup(bibDatabaseContext.getDatabasePath().get(), newName, chatMessages); + List chatMessages = + groupsChatHistory.computeIfAbsent( + groupTreeNode, + e -> + new ChatHistoryManagementRecord( + Optional.of(bibDatabaseContext), + FXCollections.observableArrayList())) + .chatHistory; + implementation.storeMessagesForGroup( + bibDatabaseContext.getDatabasePath().get(), oldName, List.of()); + implementation.storeMessagesForGroup( + bibDatabaseContext.getDatabasePath().get(), newName, chatMessages); } - private void transferEntryHistory(BibDatabaseContext bibDatabaseContext, BibEntry entry, String oldCitationKey, String newCitationKey) { + private void transferEntryHistory( + BibDatabaseContext bibDatabaseContext, + BibEntry entry, + String oldCitationKey, + String newCitationKey) { // TODO: This method does not check if the citation key is valid. if (bibDatabaseContext.getDatabasePath().isEmpty()) { - LOGGER.warn("Could not transfer chat history of entry {} (old key: {}): database path is empty.", newCitationKey, oldCitationKey); + LOGGER.warn( + "Could not transfer chat history of entry {} (old key: {}): database path is empty.", + newCitationKey, + oldCitationKey); return; } - List chatMessages = bibEntriesChatHistory.computeIfAbsent(entry, - e -> new ChatHistoryManagementRecord(Optional.of(bibDatabaseContext), FXCollections.observableArrayList())).chatHistory; - implementation.storeMessagesForGroup(bibDatabaseContext.getDatabasePath().get(), oldCitationKey, List.of()); - implementation.storeMessagesForEntry(bibDatabaseContext.getDatabasePath().get(), newCitationKey, chatMessages); + List chatMessages = + bibEntriesChatHistory.computeIfAbsent( + entry, + e -> + new ChatHistoryManagementRecord( + Optional.of(bibDatabaseContext), + FXCollections.observableArrayList())) + .chatHistory; + implementation.storeMessagesForGroup( + bibDatabaseContext.getDatabasePath().get(), oldCitationKey, List.of()); + implementation.storeMessagesForEntry( + bibDatabaseContext.getDatabasePath().get(), newCitationKey, chatMessages); } private class CitationKeyChangeListener { @@ -305,7 +412,8 @@ void listen(FieldChangedEvent e) { return; } - transferEntryHistory(bibDatabaseContext, e.getBibEntry(), e.getOldValue(), e.getNewValue()); + transferEntryHistory( + bibDatabaseContext, e.getBibEntry(), e.getOldValue(), e.getNewValue()); } } } diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java index 25e52d294e42..ddf4e33a9950 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java @@ -1,9 +1,10 @@ package org.jabref.gui.ai.components.aichat; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; +import com.airhacks.afterburner.views.ViewLoader; + +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.UserMessage; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; @@ -14,6 +15,7 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; +import org.controlsfx.control.PopOver; import org.jabref.gui.DialogService; import org.jabref.gui.ai.components.aichat.chathistory.ChatHistoryComponent; import org.jabref.gui.ai.components.aichat.chatprompt.ChatPromptComponent; @@ -34,15 +36,14 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.util.ListUtil; - -import com.airhacks.afterburner.views.ViewLoader; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.UserMessage; -import org.controlsfx.control.PopOver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + public class AiChatComponent extends VBox { private static final Logger LOGGER = LoggerFactory.getLogger(AiChatComponent.class); @@ -63,15 +64,15 @@ public class AiChatComponent extends VBox { @FXML private ChatPromptComponent chatPrompt; @FXML private Label noticeText; - public AiChatComponent(AiService aiService, - StringProperty name, - ObservableList chatHistory, - ObservableList entries, - BibDatabaseContext bibDatabaseContext, - AiPreferences aiPreferences, - DialogService dialogService, - TaskExecutor taskExecutor - ) { + 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; @@ -79,13 +80,16 @@ public AiChatComponent(AiService aiService, this.dialogService = dialogService; this.taskExecutor = taskExecutor; - this.aiChatLogic = aiService.getAiChatService().makeChat(name, chatHistory, entries, bibDatabaseContext); + this.aiChatLogic = + aiService + .getAiChatService() + .makeChat(name, chatHistory, entries, bibDatabaseContext); - aiService.getIngestionService().ingest(name, ListUtil.getLinkedFiles(entries).toList(), bibDatabaseContext); + aiService + .getIngestionService() + .ingest(name, ListUtil.getLinkedFiles(entries).toList(), bibDatabaseContext); - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -97,36 +101,48 @@ 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); } 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); chatPrompt.setCancelCallback(() -> chatPrompt.switchToNormalState()); - chatPrompt.setRetryCallback(userMessage -> { - deleteLastMessage(); - deleteLastMessage(); - chatPrompt.switchToNormalState(); - onSendMessage(userMessage); - }); + chatPrompt.setRetryCallback( + userMessage -> { + deleteLastMessage(); + deleteLastMessage(); + chatPrompt.switchToNormalState(); + onSendMessage(userMessage); + }); chatPrompt.requestPromptFocus(); @@ -135,13 +151,22 @@ private void initializeChatPrompt() { private void updateNotifications() { notifications.clear(); - notifications.addAll(entries.stream().map(this::updateNotificationsForEntry).flatMap(List::stream).toList()); + notifications.addAll( + entries.stream() + .map(this::updateNotificationsForEntry) + .flatMap(List::stream) + .toList()); notificationsButton.setVisible(!notifications.isEmpty()); 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())); } } @@ -150,46 +175,73 @@ 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") - )); + 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") - )); + 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"))); } } - 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") - )); - } - }); - - 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 will be 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() + .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"))); + } + }); - case SUCCESS -> { } - } - }); + 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 will be 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 -> {} + } + }); return notifications; } @@ -200,27 +252,33 @@ private void onSendMessage(String userPrompt) { setLoading(true); BackgroundTask task = - BackgroundTask - .wrap(() -> aiChatLogic.execute(userMessage)) + 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); - }); + .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...")); @@ -234,14 +292,13 @@ private void addError(String error) { private void updatePromptHistory() { chatPrompt.getHistory().clear(); - chatPrompt.getHistory().addAll(getReversedUserMessagesStream().map(UserMessage::singleText).toList()); + chatPrompt + .getHistory() + .addAll(getReversedUserMessagesStream().map(UserMessage::singleText).toList()); } private Stream getReversedUserMessagesStream() { - return aiChatLogic - .getChatHistory() - .reversed() - .stream() + return aiChatLogic.getChatHistory().reversed().stream() .filter(message -> message instanceof UserMessage) .map(UserMessage.class::cast); } @@ -253,10 +310,11 @@ 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(); diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java index c8f48ecb1500..30630fb8a753 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java @@ -1,5 +1,7 @@ package org.jabref.gui.ai.components.aichat; +import dev.langchain4j.data.message.ChatMessage; + import javafx.beans.property.StringProperty; import javafx.collections.ObservableList; import javafx.scene.Node; @@ -13,8 +15,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -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. */ @@ -22,7 +22,8 @@ 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 EntryEditor}, then you may not localize + /// Thus, if you use {@link AiChatGuardedComponent} for one entry in {@link EntryEditor}, then + // you may not localize /// this parameter. However, for group chat window, you should. private final StringProperty name; @@ -34,16 +35,16 @@ public class AiChatGuardedComponent extends EmbeddingModelGuardedComponent { 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, - TaskExecutor taskExecutor - ) { + public AiChatGuardedComponent( + StringProperty name, + ObservableList chatHistory, + BibDatabaseContext bibDatabaseContext, + ObservableList entries, + AiService aiService, + DialogService dialogService, + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + TaskExecutor taskExecutor) { super(aiService, aiPreferences, externalApplicationsPreferences, dialogService); this.name = name; @@ -68,7 +69,6 @@ protected Node showEmbeddingModelGuardedContent() { bibDatabaseContext, aiPreferences, dialogService, - taskExecutor - ); + taskExecutor); } } diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java index 7e4dcd8b3e1e..d476e8e0759a 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java @@ -1,5 +1,7 @@ package org.jabref.gui.ai.components.aichat; +import dev.langchain4j.data.message.ChatMessage; + import javafx.beans.property.StringProperty; import javafx.collections.ObservableList; import javafx.scene.Scene; @@ -14,8 +16,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import dev.langchain4j.data.message.ChatMessage; - public class AiChatWindow extends BaseWindow { private final AiService aiService; private final DialogService dialogService; @@ -23,15 +23,16 @@ public class AiChatWindow extends BaseWindow { private final ExternalApplicationsPreferences externalApplicationsPreferences; private final TaskExecutor taskExecutor; - // 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, - TaskExecutor taskExecutor - ) { + public AiChatWindow( + AiService aiService, + DialogService dialogService, + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + TaskExecutor taskExecutor) { this.aiService = aiService; this.dialogService = dialogService; this.aiPreferences = aiPreferences; @@ -39,7 +40,11 @@ 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( @@ -53,12 +58,9 @@ public void setChat(StringProperty name, ObservableList chatHistory dialogService, aiPreferences, externalApplicationsPreferences, - taskExecutor - ), + taskExecutor), 800, - 600 - ) - ); + 600)); } public String getChatName() { diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java index c99e125b8e7f..00014b0be57c 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java @@ -1,5 +1,9 @@ package org.jabref.gui.ai.components.aichat.chathistory; +import com.airhacks.afterburner.views.ViewLoader; + +import dev.langchain4j.data.message.ChatMessage; + import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -9,22 +13,19 @@ import org.jabref.gui.ai.components.aichat.chatmessage.ChatMessageComponent; import org.jabref.gui.util.UiTaskExecutor; -import com.airhacks.afterburner.views.ViewLoader; -import dev.langchain4j.data.message.ChatMessage; - public class ChatHistoryComponent extends ScrollPane { @FXML private VBox vBox; public ChatHistoryComponent() { - ViewLoader.view(this) - .root(this) - .load(); - - this.needsLayoutProperty().addListener((obs, oldValue, newValue) -> { - if (newValue) { - scrollDown(); - } - }); + ViewLoader.view(this).root(this).load(); + + this.needsLayoutProperty() + .addListener( + (obs, oldValue, newValue) -> { + if (newValue) { + scrollDown(); + } + }); } /** @@ -36,14 +37,23 @@ 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); - }))); - }); + UiTaskExecutor.runInJavaFXThread( + () -> { + vBox.getChildren().clear(); + items.forEach( + chatMessage -> + vBox.getChildren() + .add( + new ChatMessageComponent( + chatMessage, + chatMessageComponent -> { + int index = + vBox.getChildren() + .indexOf( + chatMessageComponent); + items.remove(index); + }))); + }); } public void scrollDown() { diff --git a/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java index 511c3d066289..8cd50be8f0e0 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java @@ -1,6 +1,11 @@ package org.jabref.gui.ai.components.aichat.chatmessage; -import java.util.function.Consumer; +import com.airhacks.afterburner.views.ViewLoader; +import com.dlsc.gemsfx.ExpandingTextArea; + +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.UserMessage; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -12,20 +17,17 @@ import org.jabref.logic.ai.util.ErrorMessage; import org.jabref.logic.l10n.Localization; - -import com.airhacks.afterburner.views.ViewLoader; -import com.dlsc.gemsfx.ExpandingTextArea; -import dev.langchain4j.data.message.AiMessage; -import dev.langchain4j.data.message.ChatMessage; -import dev.langchain4j.data.message.UserMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.function.Consumer; + 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<>(); + private final ObjectProperty> onDelete = + new SimpleObjectProperty<>(); @FXML private HBox wrapperHBox; @FXML private VBox vBox; @@ -34,18 +36,18 @@ public class ChatMessageComponent extends HBox { @FXML private VBox buttonsVBox; public ChatMessageComponent() { - ViewLoader.view(this) - .root(this) - .load(); - - chatMessage.addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - loadChatMessage(); - } - }); + ViewLoader.view(this).root(this).load(); + + chatMessage.addListener( + (observable, oldValue, newValue) -> { + if (newValue != null) { + loadChatMessage(); + } + }); } - public ChatMessageComponent(ChatMessage chatMessage, Consumer onDeleteCallback) { + public ChatMessageComponent( + ChatMessage chatMessage, Consumer onDeleteCallback) { this(); setChatMessage(chatMessage); setOnDelete(onDeleteCallback); @@ -87,7 +89,9 @@ private void loadChatMessage() { } default -> - LOGGER.error("ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", chatMessage.get().type().name()); + LOGGER.error( + "ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", + chatMessage.get().type().name()); } } @@ -104,6 +108,11 @@ 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/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java b/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java index 8bb590103acd..c69d80b3b026 100644 --- a/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.java @@ -1,6 +1,7 @@ package org.jabref.gui.ai.components.aichat.chatprompt; -import java.util.function.Consumer; +import com.airhacks.afterburner.views.ViewLoader; +import com.dlsc.gemsfx.ExpandingTextArea; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; @@ -19,8 +20,7 @@ import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.dlsc.gemsfx.ExpandingTextArea; +import java.util.function.Consumer; public class ChatPromptComponent extends HBox { // If current message that user is typing in prompt is non-existent, new, or empty, then we use @@ -31,12 +31,14 @@ public class ChatPromptComponent extends HBox { private final ObjectProperty> retryCallback = new SimpleObjectProperty<>(); private final ObjectProperty cancelCallback = new SimpleObjectProperty<>(); - private final ListProperty history = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty history = + new SimpleListProperty<>(FXCollections.observableArrayList()); // This property stores index of a user history message. // When user scrolls history in the prompt, this value is updated. // Whenever user edits the prompt, this value is reset to NEW_NON_EXISTENT_MESSAGE. - private final IntegerProperty currentUserMessageScroll = new SimpleIntegerProperty(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 user begins to edit or type a new text, then this property is false. @@ -46,14 +48,13 @@ public class ChatPromptComponent extends HBox { @FXML private Button submitButton; public ChatPromptComponent() { - ViewLoader.view(this) - .root(this) - .load(); - - history.addListener((observable, oldValue, newValue) -> { - currentUserMessageScroll.set(NEW_NON_EXISTENT_MESSAGE); - showingHistoryMessage.set(false); - }); + ViewLoader.view(this).root(this).load(); + + history.addListener( + (observable, oldValue, newValue) -> { + currentUserMessageScroll.set(NEW_NON_EXISTENT_MESSAGE); + showingHistoryMessage.set(false); + }); } public void setSendCallback(Consumer sendCallback) { @@ -74,73 +75,92 @@ public ListProperty getHistory() { @FXML private void initialize() { - userPromptTextArea.setOnKeyPressed(keyEvent -> { - if (keyEvent.getCode() == KeyCode.DOWN) { - // Do not go down in the history. - if (currentUserMessageScroll.get() != NEW_NON_EXISTENT_MESSAGE) { - showingHistoryMessage.set(true); - 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 - // will be true (this is important). - } - } 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())) { - // 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. - showingHistoryMessage.set(true); - currentUserMessageScroll.set(currentUserMessageScroll.get() + 1); - } - } 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 - // all the properties. - showingHistoryMessage.set(false); - currentUserMessageScroll.set(NEW_NON_EXISTENT_MESSAGE); - } - - if (keyEvent.getCode() == KeyCode.ENTER) { - if (keyEvent.isControlDown()) { - userPromptTextArea.appendText("\n"); + userPromptTextArea.setOnKeyPressed( + keyEvent -> { + if (keyEvent.getCode() == KeyCode.DOWN) { + // Do not go down in the history. + if (currentUserMessageScroll.get() != NEW_NON_EXISTENT_MESSAGE) { + showingHistoryMessage.set(true); + 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 + // will be true (this is important). + } + } 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())) { + // 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. + showingHistoryMessage.set(true); + currentUserMessageScroll.set(currentUserMessageScroll.get() + 1); + } } else { - onSendMessage(); + // 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 + // all the properties. + showingHistoryMessage.set(false); + currentUserMessageScroll.set(NEW_NON_EXISTENT_MESSAGE); + } + + if (keyEvent.getCode() == KeyCode.ENTER) { + if (keyEvent.isControlDown()) { + userPromptTextArea.appendText("\n"); + } else { + onSendMessage(); + } + } } - } - } - }); - - currentUserMessageScroll.addListener((observable, oldValue, newValue) -> { - // When currentUserMessageScroll is reset, then its value is - // 1) either to NEW_NON_EXISTENT_MESSAGE, - // 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)); - } - } 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. - if (showingHistoryMessage.get()) { - userPromptTextArea.setText(""); - } - } - }); + }); + + currentUserMessageScroll.addListener( + (observable, oldValue, newValue) -> { + // When currentUserMessageScroll is reset, then its value is + // 1) either to NEW_NON_EXISTENT_MESSAGE, + // 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)); + } + } 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. + if (showingHistoryMessage.get()) { + userPromptTextArea.setText(""); + } + } + }); } public void setDisableToButtons(boolean disable) { @@ -152,19 +172,21 @@ public void switchToErrorState(String userMessage) { Button retryButton = new Button(Localization.lang("Retry")); - retryButton.setOnAction(event -> { - if (retryCallback.get() != null) { - retryCallback.get().accept(userMessage); - } - }); + retryButton.setOnAction( + event -> { + if (retryCallback.get() != null) { + retryCallback.get().accept(userMessage); + } + }); Button cancelButton = new Button(Localization.lang("Cancel")); - cancelButton.setOnAction(event -> { - if (cancelCallback.get() != null) { - cancelCallback.get().run(); - } - }); + cancelButton.setOnAction( + event -> { + if (cancelCallback.get() != null) { + cancelCallback.get().run(); + } + }); this.getChildren().add(retryButton); this.getChildren().add(cancelButton); @@ -178,7 +200,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()); } diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java b/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java index a286be359bee..8b6f6acc9a16 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/AiPrivacyNoticeGuardedComponent.java @@ -17,7 +17,10 @@ public abstract class AiPrivacyNoticeGuardedComponent extends DynamicallyChangea private final ExternalApplicationsPreferences externalApplicationsPreferences; private final DialogService dialogService; - public AiPrivacyNoticeGuardedComponent(AiPreferences aiPreferences, ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService) { + public AiPrivacyNoticeGuardedComponent( + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + DialogService dialogService) { this.aiPreferences = aiPreferences; this.externalApplicationsPreferences = externalApplicationsPreferences; this.dialogService = dialogService; @@ -34,9 +37,7 @@ public final void rebuildUi() { aiPreferences, this::rebuildUi, externalApplicationsPreferences, - dialogService - ) - ); + dialogService)); } } diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java index 9681379d60d8..8670f2096ec4 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java @@ -1,6 +1,6 @@ package org.jabref.gui.ai.components.privacynotice; -import java.io.IOException; +import com.airhacks.afterburner.views.ViewLoader; import javafx.fxml.FXML; import javafx.scene.control.Hyperlink; @@ -14,11 +14,11 @@ import org.jabref.logic.ai.AiDefaultPreferences; import org.jabref.logic.ai.AiPreferences; import org.jabref.model.ai.AiProvider; - -import com.airhacks.afterburner.views.ViewLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; + public class PrivacyNoticeComponent extends ScrollPane { private final Logger LOGGER = LoggerFactory.getLogger(PrivacyNoticeComponent.class); @@ -33,15 +33,17 @@ public class PrivacyNoticeComponent extends ScrollPane { private final DialogService dialogService; private final ExternalApplicationsPreferences externalApplicationsPreferences; - public PrivacyNoticeComponent(AiPreferences aiPreferences, Runnable onIAgreeButtonClickCallback, ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService) { + public PrivacyNoticeComponent( + AiPreferences aiPreferences, + Runnable onIAgreeButtonClickCallback, + ExternalApplicationsPreferences externalApplicationsPreferences, + DialogService dialogService) { this.aiPreferences = aiPreferences; this.onIAgreeButtonClickCallback = onIAgreeButtonClickCallback; this.externalApplicationsPreferences = externalApplicationsPreferences; this.dialogService = dialogService; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -51,23 +53,32 @@ private void initialize() { initPrivacyHyperlink(geminiPrivacyTextFlow, AiProvider.GEMINI); initPrivacyHyperlink(huggingFacePrivacyTextFlow, AiProvider.HUGGING_FACE); - 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. embeddingModelText.wrappingWidthProperty().bind(this.widthProperty()); } private void initPrivacyHyperlink(TextFlow textFlow, AiProvider aiProvider) { - if (textFlow.getChildren().isEmpty() || !(textFlow.getChildren().getFirst() instanceof Text text)) { + if (textFlow.getChildren().isEmpty() + || !(textFlow.getChildren().getFirst() instanceof Text text)) { return; } - String replacedText = text.getText().replaceAll("%0", aiProvider.getLabel()).replace("%1", ""); + String replacedText = + text.getText().replaceAll("%0", aiProvider.getLabel()).replace("%1", ""); - replacedText = replacedText.endsWith(".") ? replacedText.substring(0, replacedText.length() - 1) : replacedText; + replacedText = + replacedText.endsWith(".") + ? replacedText.substring(0, replacedText.length() - 1) + : replacedText; text.setText(replacedText); text.wrappingWidthProperty().bind(this.widthProperty()); @@ -76,9 +87,10 @@ private void initPrivacyHyperlink(TextFlow textFlow, AiProvider aiProvider) { Hyperlink hyperlink = new Hyperlink(link); hyperlink.setWrapText(true); hyperlink.setFont(text.getFont()); - hyperlink.setOnAction(event -> { - openBrowser(link); - }); + hyperlink.setOnAction( + event -> { + openBrowser(link); + }); textFlow.getChildren().add(hyperlink); @@ -97,14 +109,16 @@ private void onIAgreeButtonClick() { @FXML private void onDjlPrivacyPolicyClick() { - openBrowser("https://github.com/deepjavalibrary/djl/discussions/3370#discussioncomment-10233632"); + openBrowser( + "https://github.com/deepjavalibrary/djl/discussions/3370#discussioncomment-10233632"); } private void openBrowser(String link) { try { NativeDesktop.openBrowser(link, externalApplicationsPreferences); } catch (IOException e) { - LOGGER.error("Error opening the browser to the Privacy Policy page of the AI provider.", e); + LOGGER.error( + "Error opening the browser to the Privacy Policy page of the AI provider.", e); dialogService.showErrorDialogAndWait(e); } } diff --git a/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java b/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java index ed8e66980e0e..e5a31fdf0516 100644 --- a/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java @@ -1,7 +1,5 @@ package org.jabref.gui.ai.components.summary; -import java.nio.file.Path; - import javafx.scene.Node; import org.jabref.gui.DialogService; @@ -20,10 +18,11 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; + public class SummaryComponent extends AiPrivacyNoticeGuardedComponent { private static final Logger LOGGER = LoggerFactory.getLogger(SummaryComponent.class); @@ -33,23 +32,28 @@ public class SummaryComponent extends AiPrivacyNoticeGuardedComponent { private final AiService aiService; private final AiPreferences aiPreferences; - public SummaryComponent(BibDatabaseContext bibDatabaseContext, - BibEntry entry, - AiService aiService, - AiPreferences aiPreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - CitationKeyPatternPreferences citationKeyPatternPreferences, - DialogService dialogService - ) { + public SummaryComponent( + BibDatabaseContext bibDatabaseContext, + BibEntry entry, + AiService aiService, + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + CitationKeyPatternPreferences citationKeyPatternPreferences, + DialogService dialogService) { super(aiPreferences, externalApplicationsPreferences, dialogService); this.bibDatabaseContext = bibDatabaseContext; this.entry = entry; - this.citationKeyGenerator = new CitationKeyGenerator(bibDatabaseContext, citationKeyPatternPreferences); + this.citationKeyGenerator = + new CitationKeyGenerator(bibDatabaseContext, citationKeyPatternPreferences); 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,7 +64,10 @@ protected Node showPrivacyPolicyGuardedContent() { return showErrorNoDatabasePath(); } 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)) { return tryToGenerateCitationKeyThenBind(entry); @@ -72,86 +79,95 @@ protected Node showPrivacyPolicyGuardedContent() { 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") - ); + 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.") - ); + 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).") - ); + 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.") - ); + 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"), Localization.lang("Got error while processing the file:"), processingInfo.getException().get().getLocalizedMessage(), Localization.lang("Regenerate"), - () -> aiService.getSummariesService().regenerateSummary(entry, bibDatabaseContext) - ); + () -> 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.getAiProvider().getLabel()) - ); + Localization.lang( + "The attached file(s) are currently being processed by %0. Once completed, you will be able to see the summary.", + aiPreferences.getAiProvider().getLabel())); } 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"); - return; - } - - if (entry.getCitationKey().isEmpty()) { - 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. - }); + 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"); + return; + } + + if (entry.getCitationKey().isEmpty()) { + 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. + }); } } diff --git a/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java b/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java index 3cb04347d11b..929d99316050 100644 --- a/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.java @@ -1,9 +1,6 @@ package org.jabref.gui.ai.components.summary; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Locale; +import com.airhacks.afterburner.views.ViewLoader; import javafx.fxml.FXML; import javafx.scene.control.TextArea; @@ -12,7 +9,10 @@ import org.jabref.logic.ai.summarization.Summary; -import com.airhacks.afterburner.views.ViewLoader; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.Locale; public class SummaryShowingComponent extends VBox { @FXML private TextArea summaryTextArea; @@ -25,25 +25,26 @@ 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 private void initialize() { summaryTextArea.setText(summary.content()); - 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 diff --git a/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java b/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java index a89bdf6a1612..a798de6f23d6 100644 --- a/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/util/EmbeddingModelGuardedComponent.java @@ -1,5 +1,7 @@ package org.jabref.gui.ai.components.util; +import com.google.common.eventbus.Subscribe; + import javafx.scene.Node; import org.jabref.gui.DialogService; @@ -12,8 +14,6 @@ import org.jabref.logic.ai.ingestion.model.JabRefEmbeddingModel; import org.jabref.logic.l10n.Localization; -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. @@ -21,11 +21,11 @@ public abstract class EmbeddingModelGuardedComponent extends AiPrivacyNoticeGuardedComponent { private final AiService aiService; - public EmbeddingModelGuardedComponent(AiService aiService, - AiPreferences aiPreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - DialogService dialogService - ) { + public EmbeddingModelGuardedComponent( + AiService aiService, + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + DialogService dialogService) { super(aiPreferences, externalApplicationsPreferences, dialogService); this.aiService = aiService; @@ -54,15 +54,14 @@ private Node showErrorWhileBuildingEmbeddingModel() { Localization.lang("An error occurred while building the embedding model"), aiService.getEmbeddingModel().getErrorWhileBuildingModel(), Localization.lang("Rebuild"), - () -> aiService.getEmbeddingModel().startRebuildingTask() - ); + () -> 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.") - ); + Localization.lang( + "Downloading embedding model... Afterward, you will be able to chat with your files.")); } @Subscribe diff --git a/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java b/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java index da640261ef72..2582936bb797 100644 --- a/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/util/errorstate/ErrorStateComponent.java @@ -1,5 +1,7 @@ package org.jabref.gui.ai.components.util.errorstate; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -8,17 +10,13 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; -import com.airhacks.afterburner.views.ViewLoader; - public class ErrorStateComponent extends BorderPane { @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); @@ -32,7 +30,8 @@ public static ErrorStateComponent withSpinner(String title, String content) { return errorStateComponent; } - public static ErrorStateComponent withTextArea(String title, String content, String textAreaContent) { + public static ErrorStateComponent withTextArea( + String title, String content, String textAreaContent) { ErrorStateComponent errorStateComponent = new ErrorStateComponent(title, content); TextArea textArea = new TextArea(textAreaContent); @@ -44,8 +43,14 @@ 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) { - ErrorStateComponent errorStateComponent = ErrorStateComponent.withTextArea(title, content, textAreaContent); + 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); button.setOnAction(e -> onClick.run()); diff --git a/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java b/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java index c8123974a161..52f081747587 100644 --- a/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java +++ b/src/main/java/org/jabref/gui/ai/components/util/notifications/Notification.java @@ -9,4 +9,4 @@ * 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/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java b/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java index 72ec5f271ce3..73df75aef7a1 100644 --- a/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java +++ b/src/main/java/org/jabref/gui/ai/components/util/notifications/NotificationsComponent.java @@ -1,12 +1,12 @@ package org.jabref.gui.ai.components.util.notifications; -import java.util.List; - import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.scene.control.ScrollPane; import javafx.scene.layout.VBox; +import java.util.List; + /** * A {@link ScrollPane} for displaying AI chat {@link Notification}s. See the documentation of {@link Notification} for * more details. @@ -21,7 +21,8 @@ public NotificationsComponent(ObservableList notifications) { setMaxHeight(SCROLL_PANE_MAX_HEIGHT); fill(notifications); - notifications.addListener((ListChangeListener) change -> fill(notifications)); + notifications.addListener( + (ListChangeListener) change -> fill(notifications)); } private void fill(List notifications) { diff --git a/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java b/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java index 46265c15369b..5a4afe040c24 100644 --- a/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java +++ b/src/main/java/org/jabref/gui/autocompleter/AutoCompletePreferences.java @@ -1,7 +1,5 @@ package org.jabref.gui.autocompleter; -import java.util.Set; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -12,10 +10,14 @@ import org.jabref.logic.preferences.AutoCompleteFirstNameMode; import org.jabref.model.entry.field.Field; +import java.util.Set; + public class AutoCompletePreferences { public enum NameFormat { - LAST_FIRST, FIRST_LAST, BOTH + LAST_FIRST, + FIRST_LAST, + BOTH } private final BooleanProperty shouldAutoComplete; @@ -23,10 +25,11 @@ public enum NameFormat { 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); diff --git a/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java b/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java index 89b7b520a62f..11467de62e3f 100644 --- a/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java +++ b/src/main/java/org/jabref/gui/autocompleter/AutoCompletionTextInputBinding.java @@ -26,16 +26,15 @@ */ package org.jabref.gui.autocompleter; -import java.util.Collection; - import javafx.beans.value.ChangeListener; import javafx.scene.control.TextInputControl; import javafx.util.Callback; import javafx.util.StringConverter; +import org.controlsfx.control.textfield.AutoCompletionBinding; import org.jabref.gui.util.UiTaskExecutor; -import org.controlsfx.control.textfield.AutoCompletionBinding; +import java.util.Collection; /** * Represents a binding between a text input control and an auto-completion popup @@ -48,46 +47,53 @@ 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 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 { - hidePopup(); - } - }; + private final ChangeListener focusChangedListener = + (obs, oldFocused, newFocused) -> { + if (newFocused) { + if (showOnFocus) { + setUserInputText(getCompletionTarget().getText()); + } + } else { + hidePopup(); + } + }; /** * Creates a new auto-completion binding between the given textInputControl * and the given suggestion provider. */ - private AutoCompletionTextInputBinding(final TextInputControl textInputControl, - Callback> suggestionProvider) { + private AutoCompletionTextInputBinding( + final TextInputControl textInputControl, + Callback> suggestionProvider) { - this(textInputControl, + this( + textInputControl, suggestionProvider, AutoCompletionTextInputBinding.defaultStringConverter(), new ReplaceStrategy()); } - private AutoCompletionTextInputBinding(final TextInputControl textInputControl, - final Callback> suggestionProvider, - final StringConverter converter) { + private AutoCompletionTextInputBinding( + final TextInputControl textInputControl, + 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) { + private AutoCompletionTextInputBinding( + final TextInputControl textInputControl, + final Callback> suggestionProvider, + final StringConverter converter, + final AutoCompletionStrategy inputAnalyzer) { super(textInputControl, suggestionProvider, converter); this.converter = converter; @@ -112,20 +118,37 @@ 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) { - return new AutoCompletionTextInputBinding<>(textArea, suggestionProvider, converter, 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) { diff --git a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java index a69b8a3b1c18..f5b4954700f6 100644 --- a/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/BibEntrySuggestionProvider.java @@ -1,16 +1,16 @@ package org.jabref.gui.autocompleter; -import java.util.Comparator; -import java.util.stream.Stream; +import com.google.common.base.Equivalence; +import org.controlsfx.control.textfield.AutoCompletionBinding; import org.jabref.logic.bibtex.comparator.EntryComparator; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.InternalField; import org.jabref.model.strings.StringUtil; -import com.google.common.base.Equivalence; -import org.controlsfx.control.textfield.AutoCompletionBinding; +import java.util.Comparator; +import java.util.stream.Stream; /** * Delivers possible completions as a list of {@link BibEntry} based on their citation key. @@ -37,8 +37,8 @@ protected Comparator getComparator() { protected boolean isMatch(BibEntry entry, AutoCompletionBinding.ISuggestionRequest request) { String userText = request.getUserText(); return entry.getCitationKey() - .map(key -> StringUtil.containsIgnoreCase(key, userText)) - .orElse(false); + .map(key -> StringUtil.containsIgnoreCase(key, userText)) + .orElse(false); } @Override diff --git a/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java index ebb6ec048371..34ffcde93ce3 100644 --- a/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java @@ -12,8 +12,8 @@ public class ContentSelectorSuggestionProvider extends StringSuggestionProvider private final SuggestionProvider suggestionProvider; private final List contentSelectorValues; - public ContentSelectorSuggestionProvider(SuggestionProvider suggestionProvider, - List contentSelectorValues) { + public ContentSelectorSuggestionProvider( + SuggestionProvider suggestionProvider, List contentSelectorValues) { this.suggestionProvider = suggestionProvider; this.contentSelectorValues = contentSelectorValues; diff --git a/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java index ab7df191fb53..ca81e5c02515 100644 --- a/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/EmptySuggestionProvider.java @@ -1,11 +1,12 @@ package org.jabref.gui.autocompleter; -import java.util.Comparator; -import java.util.stream.Stream; - import com.google.common.base.Equivalence; + import org.controlsfx.control.textfield.AutoCompletionBinding; +import java.util.Comparator; +import java.util.stream.Stream; + public class EmptySuggestionProvider extends SuggestionProvider { @Override protected Equivalence getEquivalence() { diff --git a/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java index 1fae672e250b..8ca3ef6f669e 100644 --- a/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/FieldValueSuggestionProvider.java @@ -1,11 +1,11 @@ package org.jabref.gui.autocompleter; -import java.util.Objects; -import java.util.stream.Stream; - import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.field.Field; +import java.util.Objects; +import java.util.stream.Stream; + /** * Stores the full content of one field. */ @@ -21,6 +21,7 @@ class FieldValueSuggestionProvider extends StringSuggestionProvider { @Override public Stream getSource() { - return database.getEntries().parallelStream().flatMap(entry -> entry.getField(field).stream()); + return database.getEntries().parallelStream() + .flatMap(entry -> entry.getField(field).stream()); } } diff --git a/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java index c0ece6855190..01f877f034e9 100644 --- a/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/JournalsSuggestionProvider.java @@ -1,18 +1,19 @@ package org.jabref.gui.autocompleter; -import java.util.stream.Stream; +import com.google.common.collect.Streams; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.field.Field; -import com.google.common.collect.Streams; +import java.util.stream.Stream; public class JournalsSuggestionProvider extends FieldValueSuggestionProvider { private final JournalAbbreviationRepository repository; - JournalsSuggestionProvider(Field field, BibDatabase database, JournalAbbreviationRepository repository) { + JournalsSuggestionProvider( + Field field, BibDatabase database, JournalAbbreviationRepository repository) { super(field, database); this.repository = repository; diff --git a/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java b/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java index c58ffda2849b..a6557d7e50fa 100644 --- a/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java +++ b/src/main/java/org/jabref/gui/autocompleter/PersonNameStringConverter.java @@ -12,7 +12,10 @@ public class PersonNameStringConverter extends StringConverter { 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; diff --git a/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java index 8457ea5dbd52..c42462aef5cb 100644 --- a/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/PersonNameSuggestionProvider.java @@ -1,12 +1,8 @@ package org.jabref.gui.autocompleter; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Stream; +import com.google.common.base.Equivalence; +import org.controlsfx.control.textfield.AutoCompletionBinding; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; @@ -14,8 +10,12 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; -import com.google.common.base.Equivalence; -import org.controlsfx.control.textfield.AutoCompletionBinding; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; /** * Delivers possible completions as a list of {@link Author}s. @@ -37,13 +37,11 @@ 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()); + return entry.getFieldMap().entrySet().stream() + .filter(fieldValuePair -> fields.contains(fieldValuePair.getKey())) + .map(Map.Entry::getValue) + .map(AuthorList::parse) + .flatMap(authors -> authors.getAuthors().stream()); } @Override @@ -58,13 +56,12 @@ protected Comparator getComparator() { @Override protected boolean isMatch(Author candidate, AutoCompletionBinding.ISuggestionRequest request) { - return StringUtil.containsIgnoreCase(candidate.getFamilyGiven(false), request.getUserText()); + return StringUtil.containsIgnoreCase( + candidate.getFamilyGiven(false), request.getUserText()); } @Override public Stream getSource() { - return database.getEntries() - .parallelStream() - .flatMap(this::getAuthors); + return database.getEntries().parallelStream().flatMap(this::getAuthors); } } diff --git a/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java index 01ba19ef39e7..d066b39976b6 100644 --- a/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/StringSuggestionProvider.java @@ -1,12 +1,12 @@ package org.jabref.gui.autocompleter; -import java.util.Comparator; -import java.util.stream.Stream; +import com.google.common.base.Equivalence; +import org.controlsfx.control.textfield.AutoCompletionBinding; import org.jabref.model.strings.StringUtil; -import com.google.common.base.Equivalence; -import org.controlsfx.control.textfield.AutoCompletionBinding; +import java.util.Comparator; +import java.util.stream.Stream; abstract class StringSuggestionProvider extends SuggestionProvider { diff --git a/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java index fb64bb372b7f..9b8ab9070f9c 100644 --- a/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java @@ -26,6 +26,10 @@ */ package org.jabref.gui.autocompleter; +import com.google.common.base.Equivalence; + +import org.controlsfx.control.textfield.AutoCompletionBinding.ISuggestionRequest; + import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -33,9 +37,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.google.common.base.Equivalence; -import org.controlsfx.control.textfield.AutoCompletionBinding.ISuggestionRequest; - /** * This is a simple implementation of a generic suggestion provider callback. * @@ -47,13 +48,17 @@ public final Collection provideSuggestions(ISuggestionRequest request) { if (!request.getUserText().isEmpty()) { 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()); + 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 { return Collections.emptyList(); } @@ -64,11 +69,15 @@ 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()); } /** diff --git a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java index 06d2abef8a8e..1c21ab2d19e0 100644 --- a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java +++ b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java @@ -1,13 +1,13 @@ package org.jabref.gui.autocompleter; -import java.util.Set; - import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldProperty; import org.jabref.model.entry.field.StandardField; +import java.util.Set; + public class SuggestionProviders { private final boolean isEmpty; @@ -15,7 +15,10 @@ public class SuggestionProviders { 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,9 +37,11 @@ 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 { return new WordSuggestionProvider(field, database); diff --git a/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java index f83d047b0f7d..3b8672d6c294 100644 --- a/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/WordSuggestionProvider.java @@ -1,11 +1,11 @@ package org.jabref.gui.autocompleter; -import java.util.Objects; -import java.util.stream.Stream; - import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.field.Field; +import java.util.Objects; +import java.util.stream.Stream; + /** * Stores all words in the given field. */ @@ -21,8 +21,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/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java index 72402b18dd56..500b64de14f4 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/AutosaveManager.java @@ -1,20 +1,20 @@ package org.jabref.gui.autosaveandbackup; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import org.jabref.logic.util.CoarseChangeFilter; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.AutosaveEvent; import org.jabref.model.database.event.BibDatabaseContextChangedEvent; - -import com.google.common.eventbus.EventBus; -import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + /** * 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. @@ -45,8 +45,8 @@ private AutosaveManager(BibDatabaseContext bibDatabaseContext) { this.executor.scheduleAtFixedRate( () -> { if (needsSave) { - eventBus.post(new AutosaveEvent()); - needsSave = false; + eventBus.post(new AutosaveEvent()); + needsSave = false; } }, DELAY_BETWEEN_AUTOSAVE_ATTEMPTS_IN_SECONDS, @@ -84,8 +84,11 @@ public static AutosaveManager start(BibDatabaseContext bibDatabaseContext) { * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ public static void shutdown(BibDatabaseContext bibDatabaseContext) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).findAny() - .ifPresent(instance -> { + runningInstances.stream() + .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) + .findAny() + .ifPresent( + instance -> { instance.shutdown(); runningInstances.remove(instance); }); diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index acae02c01c8b..591b0272e889 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -1,23 +1,6 @@ package org.jabref.gui.autosaveandbackup; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import com.google.common.eventbus.Subscribe; import javafx.scene.control.TableColumn; @@ -40,11 +23,28 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; - -import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + /** * 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 @@ -73,7 +73,11 @@ public class BackupManager { private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); private boolean needsBackup = false; - BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { + BackupManager( + LibraryTab libraryTab, + BibDatabaseContext bibDatabaseContext, + BibEntryTypesManager entryTypesManager, + CliPreferences preferences) { this.bibDatabaseContext = bibDatabaseContext; this.entryTypesManager = entryTypesManager; this.preferences = preferences; @@ -88,14 +92,16 @@ public class BackupManager { * Determines the most recent backup file name */ static Path getBackupPathForNewBackup(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(originalPath, BackupFileType.BACKUP, backupDir); + return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory( + originalPath, BackupFileType.BACKUP, backupDir); } /** * Determines the most recent existing backup file name */ static Optional getLatestBackupPath(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); + return BackupFileUtil.getPathOfLatestExistingBackupFile( + originalPath, BackupFileType.BACKUP, backupDir); } /** @@ -106,8 +112,13 @@ static Optional getLatestBackupPath(Path originalPath, Path backupDir) { * * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ - public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - BackupManager backupManager = new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + public static BackupManager start( + LibraryTab libraryTab, + BibDatabaseContext bibDatabaseContext, + BibEntryTypesManager entryTypesManager, + CliPreferences preferences) { + BackupManager backupManager = + new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences); backupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); runningInstances.add(backupManager); return backupManager; @@ -119,7 +130,9 @@ public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibD * @param bibDatabaseContext Associated {@link BibDatabaseContext} */ public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path backupDir) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.discardBackup(backupDir)); + runningInstances.stream() + .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) + .forEach(backupManager -> backupManager.discardBackup(backupDir)); } /** @@ -129,8 +142,11 @@ public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path bac * @param createBackup True, if a backup should be created * @param backupDir The path to the backup directory */ - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); + public static void shutdown( + BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { + runningInstances.stream() + .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) + .forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } @@ -157,40 +173,57 @@ public static boolean backupFileDiffers(Path originalPath, Path backupDir) { } return false; } - return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { - FileTime latestBackupFileLastModifiedTime; - try { - latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); - } 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; - } - FileTime currentFileLastModifiedTime; - try { - currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); - } 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; - } - if (latestBackupFileLastModifiedTime.compareTo(currentFileLastModifiedTime) <= 0) { - // Backup is older than current file - // We treat the backup as non-different (even if it could differ) - return false; - } - try { - boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; - if (result) { - LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); - } - return result; - } catch (IOException e) { - LOGGER.debug("Could not compare original file and backup file.", e); - // User has to investigate in this case - return true; - } - }).orElse(false); + return getLatestBackupPath(originalPath, backupDir) + .map( + latestBackupPath -> { + FileTime latestBackupFileLastModifiedTime; + try { + latestBackupFileLastModifiedTime = + Files.getLastModifiedTime(latestBackupPath); + } 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; + } + FileTime currentFileLastModifiedTime; + try { + currentFileLastModifiedTime = + Files.getLastModifiedTime(originalPath); + } 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; + } + if (latestBackupFileLastModifiedTime.compareTo( + currentFileLastModifiedTime) + <= 0) { + // Backup is older than current file + // We treat the backup as non-different (even if it could differ) + return false; + } + try { + boolean result = + Files.mismatch(originalPath, latestBackupPath) != -1L; + if (result) { + LOGGER.info( + "Backup file {} differs from current file {}", + latestBackupPath, + originalPath); + } + return result; + } catch (IOException e) { + LOGGER.debug("Could not compare original file and backup file.", e); + // User has to investigate in this case + return true; + } + }) + .orElse(false); } /** @@ -212,7 +245,9 @@ public static void restoreBackup(Path originalPath, Path backupDir) { } Optional determineBackupPathForNewBackup(Path backupDir) { - return bibDatabaseContext.getDatabasePath().map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); + return bibDatabaseContext + .getDatabasePath() + .map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); } /** @@ -238,52 +273,82 @@ void performBackup(Path backupPath) { } // 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()); - SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withMakeBackup(false) - .withSaveOrder(saveOrder) - .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + 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()); // "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::clone) - .map(BibEntry.class::cast) - .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::clone) + .map(BibEntry.class::cast) + .toList(); BibDatabase bibDatabaseClone = new BibDatabase(list); - BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); + BibDatabaseContext bibDatabaseContextClone = + new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); - Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); + 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()); + BibWriter bibWriter = + new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - entryTypesManager) - // we save the clone to prevent the original database (and thus the UI) from being changed + 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); @@ -296,7 +361,11 @@ void performBackup(Path backupPath) { } private static Path determineDiscardedFile(Path file, Path backupDir) { - return backupDir.resolve(BackupFileUtil.getUniqueFilePrefix(file) + "--" + file.getFileName() + "--discarded"); + return backupDir.resolve( + BackupFileUtil.getUniqueFilePrefix(file) + + "--" + + file.getFileName() + + "--discarded"); } /** @@ -328,7 +397,8 @@ private void logIfCritical(Path backupPath, IOException e) { } @Subscribe - public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) { + public synchronized void listen( + @SuppressWarnings("unused") BibDatabaseContextChangedEvent event) { if (!event.isFilteredOut()) { this.needsBackup = true; } @@ -338,30 +408,47 @@ 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(path -> this.performBackup(path)), - 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(path -> this.performBackup(path)), + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + TimeUnit.SECONDS); } private void fillQueue(Path backupDir) { if (!Files.exists(backupDir)) { return; } - bibDatabaseContext.getDatabasePath().ifPresent(databasePath -> { - // 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(); - backupFilesQueue.addAll(allSavFiles); - } catch (IOException e) { - LOGGER.error("Could not determine most recent file", e); - } - }); + bibDatabaseContext + .getDatabasePath() + .ifPresent( + databasePath -> { + // 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(); + backupFilesQueue.addAll(allSavFiles); + } catch (IOException e) { + LOGGER.error("Could not determine most recent file", e); + } + }); } /** diff --git a/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java b/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java index 511ef2d168a1..d3e903efa592 100644 --- a/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java +++ b/src/main/java/org/jabref/gui/auximport/AuxParserResultViewModel.java @@ -1,10 +1,10 @@ package org.jabref.gui.auximport; -import java.util.stream.Collectors; - import org.jabref.logic.auxparser.AuxParserResult; import org.jabref.logic.l10n.Localization; +import java.util.stream.Collectors; + public class AuxParserResultViewModel { private AuxParserResult auxParserResult; @@ -21,18 +21,41 @@ public AuxParserResultViewModel(AuxParserResult auxParserResult) { public String getInformation(boolean includeMissingEntries) { String missingEntries = ""; if (includeMissingEntries && (this.auxParserResult.getUnresolvedKeysCount() > 0)) { - missingEntries = this.auxParserResult.getUnresolvedKeys().stream().collect(Collectors.joining(", ", " (", ")")); + missingEntries = + this.auxParserResult.getUnresolvedKeys().stream() + .collect(Collectors.joining(", ", " (", ")")); } StringBuilder result = new StringBuilder(); - result.append(Localization.lang("keys in library")).append(' ').append(this.auxParserResult.getMasterDatabase().getEntryCount()).append('\n') - .append(Localization.lang("found in AUX file")).append(' ').append(this.auxParserResult.getFoundKeysInAux()).append('\n') - .append(Localization.lang("resolved")).append(' ').append(this.auxParserResult.getResolvedKeysCount()).append('\n') - .append(Localization.lang("not found")).append(' ').append(this.auxParserResult.getUnresolvedKeysCount()).append(missingEntries).append('\n') - .append(Localization.lang("crossreferenced entries included")).append(' ').append(this.auxParserResult.getCrossRefEntriesCount()).append('\n') - .append(Localization.lang("strings included")).append(' ').append(this.auxParserResult.getInsertedStrings()).append('\n'); + result.append(Localization.lang("keys in library")) + .append(' ') + .append(this.auxParserResult.getMasterDatabase().getEntryCount()) + .append('\n') + .append(Localization.lang("found in AUX file")) + .append(' ') + .append(this.auxParserResult.getFoundKeysInAux()) + .append('\n') + .append(Localization.lang("resolved")) + .append(' ') + .append(this.auxParserResult.getResolvedKeysCount()) + .append('\n') + .append(Localization.lang("not found")) + .append(' ') + .append(this.auxParserResult.getUnresolvedKeysCount()) + .append(missingEntries) + .append('\n') + .append(Localization.lang("crossreferenced entries included")) + .append(' ') + .append(this.auxParserResult.getCrossRefEntriesCount()) + .append('\n') + .append(Localization.lang("strings included")) + .append(' ') + .append(this.auxParserResult.getInsertedStrings()) + .append('\n'); if (this.auxParserResult.getNestedAuxCount() > 0) { - result.append(Localization.lang("nested AUX files")).append(' ').append(this.auxParserResult.getNestedAuxCount()); + result.append(Localization.lang("nested AUX files")) + .append(' ') + .append(this.auxParserResult.getNestedAuxCount()); } return result.toString(); } diff --git a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java index f60123a2b948..5f02e6db920f 100644 --- a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java +++ b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java @@ -1,5 +1,10 @@ package org.jabref.gui.auximport; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; + import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; @@ -18,10 +23,6 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.database.BibDatabaseContext; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; - /** * A wizard dialog for generating a new sub database from existing TeX AUX file */ @@ -44,26 +45,26 @@ 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()); generateButton.defaultButtonProperty().bind(generateButton.disableProperty().not()); - setResultConverter(button -> { - if (button == generateButtonType) { - viewModel.addResultToTabContainer(); - } - return null; - }); + setResultConverter( + button -> { + if (button == generateButtonType) { + viewModel.addResultToTabContainer(); + } + return null; + }); themeManager.updateFontStyle(getDialogPane().getScene()); } @FXML private void initialize() { - viewModel = new FromAuxDialogViewModel(tabContainer, dialogService, preferences, stateManager); + viewModel = + new FromAuxDialogViewModel(tabContainer, dialogService, preferences, stateManager); auxFileField.textProperty().bindBidirectional(viewModel.auxFileProperty()); statusInfos.textProperty().bindBidirectional(viewModel.statusTextProperty()); @@ -75,7 +76,9 @@ private void initialize() { new ViewModelListCellFactory() .withText(viewModel::getDatabaseName) .install(libraryListView); - EasyBind.listen(libraryListView.getSelectionModel().selectedItemProperty(), (obs, oldValue, newValue) -> parseActionPerformed()); + EasyBind.listen( + libraryListView.getSelectionModel().selectedItemProperty(), + (obs, oldValue, newValue) -> parseActionPerformed()); } @FXML diff --git a/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java b/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java index eb871f44a225..be31383f858e 100644 --- a/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java +++ b/src/main/java/org/jabref/gui/auximport/FromAuxDialogViewModel.java @@ -1,7 +1,6 @@ package org.jabref.gui.auximport; -import java.nio.file.Path; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -29,16 +28,20 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; -import com.tobiasdiez.easybind.EasyBind; +import java.nio.file.Path; +import java.util.Optional; 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 ObjectProperty selectedLibraryProperty = new SimpleObjectProperty<>(); + private final ListProperty notFoundList = + 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; @@ -47,10 +50,11 @@ public class FromAuxDialogViewModel { 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; @@ -58,31 +62,44 @@ public FromAuxDialogViewModel(LibraryTabContainer tabContainer, librariesProperty.setAll(stateManager.getOpenDatabases()); selectedLibraryProperty.set(tabContainer.getCurrentLibraryTab().getBibDatabaseContext()); - EasyBind.listen(selectedLibraryProperty, (obs, oldValue, newValue) -> { - if (auxParserResult != null) { - parse(); - } - }); + EasyBind.listen( + selectedLibraryProperty, + (obs, oldValue, newValue) -> { + if (auxParserResult != null) { + parse(); + } + }); } public String getDatabaseName(BibDatabaseContext databaseContext) { Optional dbOpt = Optional.empty(); if (databaseContext.getDatabasePath().isPresent()) { - dbOpt = FileUtil.getUniquePathFragment(stateManager.collectAllDatabasePaths(), databaseContext.getDatabasePath().get()); + dbOpt = + FileUtil.getUniquePathFragment( + stateManager.collectAllDatabasePaths(), + databaseContext.getDatabasePath().get()); } if (databaseContext.getLocation() == DatabaseLocation.SHARED) { - return databaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"; + return databaseContext.getDBMSSynchronizer().getDBName() + + " [" + + Localization.lang("shared") + + "]"; } return dbOpt.orElse(Localization.lang("untitled")); } 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())); + 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())); } public void parse() { @@ -96,11 +113,13 @@ public void parse() { AuxParser auxParser = new DefaultAuxParser(referenceDatabase); auxParserResult = auxParser.parse(Path.of(auxName)); notFoundList.setAll(auxParserResult.getUnresolvedKeys()); - statusTextProperty.set(new AuxParserResultViewModel(auxParserResult).getInformation(false)); + statusTextProperty.set( + new AuxParserResultViewModel(auxParserResult).getInformation(false)); if (!auxParserResult.getGeneratedBibDatabase().hasEntries()) { // The generated database contains no entries -> no active generate-button - statusTextProperty.set(statusTextProperty.get() + "\n" + Localization.lang("empty library")); + statusTextProperty.set( + statusTextProperty.get() + "\n" + Localization.lang("empty library")); parseFailedProperty.set(true); } } else { @@ -109,7 +128,8 @@ public void parse() { } public void addResultToTabContainer() { - BibDatabaseContext context = new BibDatabaseContext(auxParserResult.getGeneratedBibDatabase()); + BibDatabaseContext context = + new BibDatabaseContext(auxParserResult.getGeneratedBibDatabase()); tabContainer.addTab(context, true); } diff --git a/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java b/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java index 2af0171b5da7..f7f71d5efbd7 100644 --- a/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java +++ b/src/main/java/org/jabref/gui/auximport/NewSubLibraryAction.java @@ -1,12 +1,12 @@ package org.jabref.gui.auximport; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; - /** * The action concerned with generate a new (sub-)database from latex AUX file. * @@ -17,7 +17,10 @@ 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; diff --git a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java index cf35336765d2..32894d3beed6 100644 --- a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java @@ -1,61 +1,81 @@ package org.jabref.gui.backup; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.Hyperlink; +import org.controlsfx.control.HyperlinkLabel; import org.jabref.gui.FXDialog; import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.io.BackupFileUtil; - -import org.controlsfx.control.HyperlinkLabel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + 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); contentLabel.setPrefWidth(360); - contentLabel.setOnAction(e -> { - if (backupPathOpt.isPresent()) { - if (!(e.getSource() instanceof Hyperlink)) { - return; - } - String clickedLinkText = ((Hyperlink) (e.getSource())).getText(); - if (backupFilename.equals(clickedLinkText)) { - try { - NativeDesktop.openFolderAndSelectFile(backupPathOpt.get(), externalApplicationsPreferences, null); - } catch (IOException ex) { - LOGGER.error("Could not open backup folder", ex); + contentLabel.setOnAction( + e -> { + if (backupPathOpt.isPresent()) { + if (!(e.getSource() instanceof Hyperlink)) { + return; + } + String clickedLinkText = ((Hyperlink) (e.getSource())).getText(); + if (backupFilename.equals(clickedLinkText)) { + try { + NativeDesktop.openFolderAndSelectFile( + backupPathOpt.get(), externalApplicationsPreferences, null); + } catch (IOException ex) { + LOGGER.error("Could not open backup folder", ex); + } + } } - } - } - }); + }); getDialogPane().setContent(contentLabel); } } diff --git a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractor.java b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractor.java index 98b29620e5bc..b24dc50cacfc 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractor.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractor.java @@ -1,5 +1,10 @@ package org.jabref.gui.bibtexextractor; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.EntryType; +import org.jabref.model.entry.types.StandardEntryType; + import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -7,11 +12,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.entry.types.EntryType; -import org.jabref.model.entry.types.StandardEntryType; - public class BibtexExtractor { private static final String AUTHOR_TAG = "[author_tag]"; @@ -22,29 +22,41 @@ public class BibtexExtractor { private static final String INITIALS_GROUP = "INITIALS"; private static final String LASTNAME_GROUP = "LASTNAME"; - private static final Pattern URL_PATTERN = Pattern.compile( - "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + - "(([\\w\\-]+\\.)+?([\\w\\-.~]+\\/?)*" + - "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); - - private static final Pattern YEAR_PATTERN = Pattern.compile( - "\\d{4}", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); - - private static final Pattern AUTHOR_PATTERN = Pattern.compile( - "(?<" + LASTNAME_GROUP + ">\\p{Lu}\\w+),?\\s(?<" + INITIALS_GROUP + ">(\\p{Lu}\\.\\s){1,2})" + - "\\s*(and|,|\\.)*", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); - - private static final Pattern AUTHOR_PATTERN_2 = Pattern.compile( - "(?<" + INITIALS_GROUP + ">(\\p{Lu}\\.\\s){1,2})(?<" + LASTNAME_GROUP + ">\\p{Lu}\\w+)" + - "\\s*(and|,|\\.)*", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); - - private static final Pattern PAGES_PATTERN = Pattern.compile( - "(p.)?\\s?\\d+(-\\d+)?", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + private static final Pattern URL_PATTERN = + Pattern.compile( + "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + + "(([\\w\\-]+\\.)+?([\\w\\-.~]+\\/?)*" + + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + private static final Pattern YEAR_PATTERN = + Pattern.compile( + "\\d{4}", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + private static final Pattern AUTHOR_PATTERN = + Pattern.compile( + "(?<" + + LASTNAME_GROUP + + ">\\p{Lu}\\w+),?\\s(?<" + + INITIALS_GROUP + + ">(\\p{Lu}\\.\\s){1,2})" + + "\\s*(and|,|\\.)*", + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + private static final Pattern AUTHOR_PATTERN_2 = + Pattern.compile( + "(?<" + + INITIALS_GROUP + + ">(\\p{Lu}\\.\\s){1,2})(?<" + + LASTNAME_GROUP + + ">\\p{Lu}\\w+)" + + "\\s*(and|,|\\.)*", + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + private static final Pattern PAGES_PATTERN = + Pattern.compile( + "(p.)?\\s?\\d+(-\\d+)?", + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); private final List urls = new ArrayList<>(); private final List authors = new ArrayList<>(); @@ -93,7 +105,8 @@ private String findYear(String input) { while (matcher.find()) { String yearCandidate = input.substring(matcher.start(), matcher.end()); int intYearCandidate = Integer.parseInt(yearCandidate); - if ((intYearCandidate > 1700) && (intYearCandidate <= Calendar.getInstance().get(Calendar.YEAR))) { + if ((intYearCandidate > 1700) + && (intYearCandidate <= Calendar.getInstance().get(Calendar.YEAR))) { year = yearCandidate; return fixSpaces(input.replace(year, YEAR_TAG)); } @@ -109,7 +122,8 @@ private String findAuthors(String input) { private String findAuthorsByPattern(String input, Pattern pattern) { Matcher matcher = pattern.matcher(input); while (matcher.find()) { - authors.add(GenerateAuthor(matcher.group(LASTNAME_GROUP), matcher.group(INITIALS_GROUP))); + authors.add( + GenerateAuthor(matcher.group(LASTNAME_GROUP), matcher.group(INITIALS_GROUP))); } return fixSpaces(matcher.replaceAll(AUTHOR_TAG)); } @@ -128,8 +142,9 @@ private String findPages(String input) { private String fixSpaces(String input) { return input.replaceAll("[,.!?;:]", "$0 ") - .replaceAll("\\p{Lt}", " $0") - .replaceAll("\\s+", " ").trim(); + .replaceAll("\\p{Lt}", " $0") + .replaceAll("\\s+", " ") + .trim(); } private String findParts(String input) { @@ -142,9 +157,10 @@ private String findParts(String input) { } int delimiterIndex = input.lastIndexOf("//"); if (delimiterIndex != -1) { - lastParts.add(input.substring(afterAuthorsIndex, delimiterIndex) - .replace(YEAR_TAG, "") - .replace(PAGES_TAG, "")); + lastParts.add( + input.substring(afterAuthorsIndex, delimiterIndex) + .replace(YEAR_TAG, "") + .replace(PAGES_TAG, "")); lastParts.addAll(Arrays.asList(input.substring(delimiterIndex + 2).split(",|\\."))); } else { lastParts.addAll(Arrays.asList(input.substring(afterAuthorsIndex).split(",|\\."))); diff --git a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java index 4e1ea80e0e16..789140f9a942 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.bibtexextractor; -import java.util.List; - -import javax.swing.undo.UndoManager; - import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -20,10 +16,13 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.util.FileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + +import javax.swing.undo.UndoManager; + /** * View model for the feature "Extract BibTeX from plain text". * Handles both online and offline case. @@ -42,26 +41,28 @@ public class BibtexExtractorViewModel { private final ImportHandler importHandler; private final StringProperty inputTextProperty = new SimpleStringProperty(""); - public BibtexExtractorViewModel(boolean onlineMode, - BibDatabaseContext bibdatabaseContext, - DialogService dialogService, - GuiPreferences preferences, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - UndoManager undoManager, - StateManager stateManager) { + public BibtexExtractorViewModel( + boolean onlineMode, + BibDatabaseContext bibdatabaseContext, + DialogService dialogService, + GuiPreferences preferences, + FileUpdateMonitor fileUpdateMonitor, + TaskExecutor taskExecutor, + UndoManager undoManager, + StateManager stateManager) { this.onlineMode = onlineMode; this.dialogService = dialogService; this.preferences = preferences; this.taskExecutor = taskExecutor; - this.importHandler = new ImportHandler( - bibdatabaseContext, - preferences, - fileUpdateMonitor, - undoManager, - stateManager, - dialogService, - taskExecutor); + this.importHandler = + new ImportHandler( + bibdatabaseContext, + preferences, + fileUpdateMonitor, + undoManager, + stateManager, + dialogService, + taskExecutor); } public void startParsing() { @@ -78,22 +79,36 @@ private void startParsingOffline() { } private void startParsingOnline() { - GrobidCitationFetcher grobidCitationFetcher = new GrobidCitationFetcher(preferences.getGrobidPreferences(), preferences.getImportFormatPreferences()); + GrobidCitationFetcher grobidCitationFetcher = + new GrobidCitationFetcher( + preferences.getGrobidPreferences(), + preferences.getImportFormatPreferences()); BackgroundTask.wrap(() -> grobidCitationFetcher.performSearch(inputTextProperty.getValue())) - .onRunning(() -> dialogService.notify(Localization.lang("Your text is being parsed..."))) - .onFailure(e -> { - if (e instanceof FetcherException) { - String msg = Localization.lang("There are connection issues with a JabRef server. Detailed information: %0", - e.getMessage()); - dialogService.notify(msg); - } else { - LOGGER.warn("Missing exception handling.", e); - } - }) - .onSuccess(parsedEntries -> { - dialogService.notify(Localization.lang("%0 entries were parsed from your query.", String.valueOf(parsedEntries.size()))); - importHandler.importEntries(parsedEntries); - }).executeWith(taskExecutor); + .onRunning( + () -> + dialogService.notify( + Localization.lang("Your text is being parsed..."))) + .onFailure( + e -> { + if (e instanceof FetcherException) { + String msg = + Localization.lang( + "There are connection issues with a JabRef server. Detailed information: %0", + e.getMessage()); + dialogService.notify(msg); + } else { + LOGGER.warn("Missing exception handling.", e); + } + }) + .onSuccess( + parsedEntries -> { + dialogService.notify( + Localization.lang( + "%0 entries were parsed from your query.", + String.valueOf(parsedEntries.size()))); + importHandler.importEntries(parsedEntries); + }) + .executeWith(taskExecutor); } public StringProperty inputTextProperty() { diff --git a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOffline.java b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOffline.java index ddd1eba3dda4..8b179264449e 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOffline.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOffline.java @@ -1,11 +1,11 @@ package org.jabref.gui.bibtexextractor; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; - public class ExtractBibtexActionOffline extends SimpleCommand { private final DialogService dialogService; diff --git a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOnline.java b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOnline.java index 043076067e4b..d0144137c57d 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOnline.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexActionOnline.java @@ -1,5 +1,7 @@ package org.jabref.gui.bibtexextractor; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + import javafx.beans.binding.Bindings; import org.jabref.gui.DialogService; @@ -8,22 +10,23 @@ import org.jabref.gui.importer.GrobidOptInDialogHelper; import org.jabref.logic.preferences.CliPreferences; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; - public class ExtractBibtexActionOnline extends SimpleCommand { private final CliPreferences preferences; private final DialogService dialogService; - public ExtractBibtexActionOnline(DialogService dialogService, CliPreferences preferences, StateManager stateManager, boolean requiresGrobid) { + public ExtractBibtexActionOnline( + DialogService dialogService, + CliPreferences preferences, + StateManager stateManager, + boolean requiresGrobid) { this.preferences = preferences; this.dialogService = dialogService; if (requiresGrobid) { this.executable.bind( Bindings.and( preferences.getGrobidPreferences().grobidEnabledProperty(), - needsDatabase(stateManager) - )); + needsDatabase(stateManager))); } else { this.executable.bind(needsDatabase(stateManager)); } @@ -31,7 +34,9 @@ public ExtractBibtexActionOnline(DialogService dialogService, CliPreferences pre @Override public void execute() { - boolean useGrobid = GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); + boolean useGrobid = + GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided( + dialogService, preferences.getGrobidPreferences()); dialogService.showCustomDialogAndWait(new ExtractBibtexDialog(useGrobid)); } } diff --git a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java index bf6266630b4a..aef116bc9968 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/ExtractBibtexDialog.java @@ -1,6 +1,8 @@ package org.jabref.gui.bibtexextractor; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; @@ -20,8 +22,7 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import javax.swing.undo.UndoManager; /** * GUI Dialog for the feature "Extract BibTeX from plain text". @@ -46,9 +47,7 @@ public class ExtractBibtexDialog extends BaseDialog { public ExtractBibtexDialog(boolean onlineMode) { this.onlineMode = onlineMode; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); if (onlineMode) { this.setTitle(Localization.lang("Plain References Parser (online)")); } else { @@ -58,32 +57,42 @@ public ExtractBibtexDialog(boolean onlineMode) { @FXML private void initialize() { - BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); - BibtexExtractorViewModel viewModel = new BibtexExtractorViewModel( - onlineMode, - database, - dialogService, - preferences, - fileUpdateMonitor, - taskExecutor, - undoManager, - stateManager); + BibDatabaseContext database = + stateManager + .getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); + BibtexExtractorViewModel viewModel = + new BibtexExtractorViewModel( + onlineMode, + database, + dialogService, + preferences, + fileUpdateMonitor, + taskExecutor, + undoManager, + stateManager); input.textProperty().bindBidirectional(viewModel.inputTextProperty()); String clipText = ClipBoardManager.getContents(); if (StringUtil.isBlank(clipText)) { - input.setPromptText(Localization.lang("Please enter the plain references to extract from separated by double empty lines.")); + input.setPromptText( + Localization.lang( + "Please enter the plain references to extract from separated by double empty lines.")); } else { input.setText(clipText); input.selectAll(); } - Platform.runLater(() -> { - input.requestFocus(); - Button buttonParse = (Button) getDialogPane().lookupButton(parseButtonType); - buttonParse.setTooltip(new Tooltip((Localization.lang("Starts the extraction and adds the resulting entries to the currently opened database")))); - buttonParse.setOnAction(event -> viewModel.startParsing()); - buttonParse.disableProperty().bind(viewModel.inputTextProperty().isEmpty()); - }); + Platform.runLater( + () -> { + input.requestFocus(); + Button buttonParse = (Button) getDialogPane().lookupButton(parseButtonType); + buttonParse.setTooltip( + new Tooltip( + (Localization.lang( + "Starts the extraction and adds the resulting entries to the currently opened database")))); + buttonParse.setOnAction(event -> viewModel.startParsing()); + buttonParse.disableProperty().bind(viewModel.inputTextProperty().isEmpty()); + }); } } diff --git a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java index ce661fdff534..cfe53e9ada00 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeyAction.java @@ -1,11 +1,5 @@ package org.jabref.gui.citationkeypattern; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -21,6 +15,12 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.BibEntry; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.swing.undo.UndoManager; + public class GenerateCitationKeyAction extends SimpleCommand { private final Supplier tabSupplier; @@ -34,12 +34,13 @@ public class GenerateCitationKeyAction extends SimpleCommand { 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; @@ -55,11 +56,15 @@ public void execute() { entries = stateManager.getSelectedEntries(); if (entries.isEmpty()) { - dialogService.showWarningDialogAndWait(Localization.lang("Autogenerate citation keys"), - Localization.lang("First select the entries you want keys to be generated for.")); + dialogService.showWarningDialogAndWait( + Localization.lang("Autogenerate citation keys"), + Localization.lang( + "First select the entries you want keys to be generated for.")); return; } - dialogService.notify(formatOutputMessage(Localization.lang("Generating citation key for"), entries.size())); + dialogService.notify( + formatOutputMessage( + Localization.lang("Generating citation key for"), entries.size())); checkOverwriteKeysChosen(); @@ -67,13 +72,16 @@ public void execute() { BackgroundTask backgroundTask = this.generateKeysInBackground(); backgroundTask.showToUser(true); backgroundTask.titleProperty().set(Localization.lang("Autogenerate citation keys")); - backgroundTask.messageProperty().set(Localization.lang("%0/%1 entries", 0, entries.size())); + backgroundTask + .messageProperty() + .set(Localization.lang("%0/%1 entries", 0, entries.size())); backgroundTask.executeWith(this.taskExecutor); } } - public static boolean confirmOverwriteKeys(DialogService dialogService, CliPreferences preferences) { + public static boolean confirmOverwriteKeys( + DialogService dialogService, CliPreferences preferences) { if (preferences.getCitationKeyPatternPreferences().shouldWarnBeforeOverwriteCiteKey()) { return dialogService.showConfirmationDialogWithOptOutAndWait( Localization.lang("Overwrite keys"), @@ -81,7 +89,10 @@ public static boolean confirmOverwriteKeys(DialogService dialogService, CliPrefe Localization.lang("Overwrite keys"), Localization.lang("Cancel"), Localization.lang("Do not ask again"), - optOut -> preferences.getCitationKeyPatternPreferences().setWarnBeforeOverwriteCiteKey(!optOut)); + optOut -> + preferences + .getCitationKeyPatternPreferences() + .setWarnBeforeOverwriteCiteKey(!optOut)); } else { // Always overwrite keys by default return true; @@ -109,32 +120,54 @@ 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,14 +178,19 @@ 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); } }; } private String formatOutputMessage(String start, int count) { - return "%s %d %s.".formatted(start, count, - (count > 1 ? Localization.lang("entries") : Localization.lang("entry"))); + return "%s %d %s." + .formatted( + start, + count, + (count > 1 ? Localization.lang("entries") : Localization.lang("entry"))); } } diff --git a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java index 3b99157d6aa7..2395f27d681e 100644 --- a/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java +++ b/src/main/java/org/jabref/gui/citationkeypattern/GenerateCitationKeySingleAction.java @@ -1,7 +1,5 @@ package org.jabref.gui.citationkeypattern; -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.undo.UndoableKeyChange; @@ -10,6 +8,8 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import javax.swing.undo.UndoManager; + public class GenerateCitationKeySingleAction extends SimpleCommand { private final DialogService dialogService; @@ -18,7 +18,12 @@ public class GenerateCitationKeySingleAction extends SimpleCommand { 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; @@ -32,8 +37,10 @@ public GenerateCitationKeySingleAction(BibEntry entry, BibDatabaseContext databa @Override public void execute() { - if (!entry.hasCitationKey() || GenerateCitationKeyAction.confirmOverwriteKeys(dialogService, preferences)) { - new CitationKeyGenerator(databaseContext, preferences.getCitationKeyPatternPreferences()) + if (!entry.hasCitationKey() + || GenerateCitationKeyAction.confirmOverwriteKeys(dialogService, preferences)) { + new CitationKeyGenerator( + databaseContext, preferences.getCitationKeyPatternPreferences()) .generateAndSetKey(entry) .ifPresent(change -> undoManager.addEdit(new UndoableKeyChange(change))); } diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupAction.java b/src/main/java/org/jabref/gui/cleanup/CleanupAction.java index 9fdac23cd88e..0893bf5aab59 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupAction.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupAction.java @@ -1,11 +1,5 @@ package org.jabref.gui.cleanup; -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -23,6 +17,12 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import javax.swing.undo.UndoManager; + public class CleanupAction extends SimpleCommand { private final Supplier tabSupplier; @@ -35,12 +35,13 @@ public class CleanupAction extends SimpleCommand { 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; @@ -57,55 +58,76 @@ 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() - ); - - 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?"), - Localization.lang("Autogenerate PDF Names"), - Localization.lang("Cancel"), - Localization.lang("Do not ask again"), - optOut -> preferences.getAutoLinkPreferences().setAskAutoNamingPdfs(!optOut)); - if (!confirmed) { - isCanceled = true; - return; - } - } - - preferences.getCleanupPreferences().setActiveJobs(preset.getActiveJobs()); - preferences.getCleanupPreferences().setFieldFormatterCleanups(preset.getFieldFormatterCleanups()); - - BackgroundTask.wrap(() -> cleanup(stateManager.getActiveDatabase().get(), preset)) - .onSuccess(result -> showResults()) - .onFailure(dialogService::showErrorDialogAndWait) - .executeWith(taskExecutor); - }); + 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?"), + Localization.lang("Autogenerate PDF Names"), + Localization.lang("Cancel"), + Localization.lang("Do not ask again"), + optOut -> + preferences + .getAutoLinkPreferences() + .setAskAutoNamingPdfs(!optOut)); + if (!confirmed) { + isCanceled = true; + return; + } + } + + preferences.getCleanupPreferences().setActiveJobs(preset.getActiveJobs()); + preferences + .getCleanupPreferences() + .setFieldFormatterCleanups(preset.getFieldFormatterCleanups()); + + BackgroundTask.wrap( + () -> cleanup(stateManager.getActiveDatabase().get(), preset)) + .onSuccess(result -> showResults()) + .onFailure(dialogService::showErrorDialogAndWait) + .executeWith(taskExecutor); + }); } /** * 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); @@ -130,11 +152,15 @@ private void showResults() { } 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))); + dialogService.notify( + Localization.lang( + "%0 entries needed a clean up", + Integer.toString(modifiedEntriesCount))); } } - private void cleanup(BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences) { + private void cleanup( + BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences) { for (BibEntry entry : stateManager.getSelectedEntries()) { // undo granularity is on entry level NamedCompound ce = new NamedCompound(Localization.lang("Cleanup entry")); diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java b/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java index 380dea76fe23..24c55a8064a0 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupDialog.java @@ -10,12 +10,16 @@ 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("Cleanup entries")); getDialogPane().setPrefSize(600, 650); getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); - CleanupPresetPanel presetPanel = new CleanupPresetPanel(databaseContext, initialPreset, filePreferences); + CleanupPresetPanel presetPanel = + new CleanupPresetPanel(databaseContext, initialPreset, filePreferences); // placing the content of the presetPanel in a scroll pane ScrollPane scrollPane = new ScrollPane(); @@ -24,12 +28,13 @@ public CleanupDialog(BibDatabaseContext databaseContext, CleanupPreferences init scrollPane.setContent(presetPanel); getDialogPane().setContent(scrollPane); - setResultConverter(button -> { - if (button == ButtonType.OK) { - return presetPanel.getCleanupPreset(); - } else { - return null; - } - }); + setResultConverter( + button -> { + if (button == ButtonType.OK) { + return presetPanel.getCleanupPreset(); + } else { + return null; + } + }); } } diff --git a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java index a31f385781db..9345aad7748a 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupPresetPanel.java @@ -1,9 +1,6 @@ package org.jabref.gui.cleanup; -import java.nio.file.Path; -import java.util.EnumSet; -import java.util.Objects; -import java.util.Optional; +import com.airhacks.afterburner.views.ViewLoader; import javafx.collections.FXCollections; import javafx.fxml.FXML; @@ -19,7 +16,10 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.field.StandardField; -import com.airhacks.afterburner.views.ViewLoader; +import java.nio.file.Path; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Optional; public class CleanupPresetPanel extends VBox { @@ -41,13 +41,14 @@ public class CleanupPresetPanel extends VBox { @FXML private CheckBox cleanUpTimestampToModificationDate; @FXML private FieldFormatterCleanupsPanel formatterCleanupsPanel; - public CleanupPresetPanel(BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences, FilePreferences filePreferences) { + 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 +56,69 @@ 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())); + 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", "...")); + 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()); + 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") - .concat(": ") - .concat(filePreferences.getFileNamePattern()); + String currentPattern = + Localization.lang("Filename format pattern") + .concat(": ") + .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); } @@ -107,23 +129,40 @@ private void updateDisplay(CleanupPreferences preset) { if (!cleanUpMovePDF.isDisabled()) { cleanUpMovePDF.setSelected(preset.isActive(CleanupPreferences.CleanupStep.MOVE_PDF)); } - cleanUpMakePathsRelative.setSelected(preset.isActive(CleanupPreferences.CleanupStep.MAKE_PATHS_RELATIVE)); + 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)); - 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)); + 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)); cleanUpISSN.setSelected(preset.isActive(CleanupPreferences.CleanupStep.CLEAN_UP_ISSN)); - formatterCleanupsPanel.cleanupsDisableProperty().setValue(!preset.getFieldFormatterCleanups().isEnabled()); - formatterCleanupsPanel.cleanupsProperty().setValue(FXCollections.observableArrayList(preset.getFieldFormatterCleanups().getConfiguredActions())); + formatterCleanupsPanel + .cleanupsDisableProperty() + .setValue(!preset.getFieldFormatterCleanups().isEnabled()); + formatterCleanupsPanel + .cleanupsProperty() + .setValue( + FXCollections.observableArrayList( + preset.getFieldFormatterCleanups().getConfiguredActions())); } public CleanupPreferences getCleanupPreset() { - EnumSet activeJobs = EnumSet.noneOf(CleanupPreferences.CleanupStep.class); + EnumSet activeJobs = + EnumSet.noneOf(CleanupPreferences.CleanupStep.class); if (cleanUpMovePDF.isSelected()) { activeJobs.add(CleanupPreferences.CleanupStep.MOVE_PDF); @@ -171,8 +210,10 @@ 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/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java b/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java index 4688ca6c67ab..3e3c77712c35 100644 --- a/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java +++ b/src/main/java/org/jabref/gui/cleanup/CleanupSingleAction.java @@ -1,10 +1,5 @@ package org.jabref.gui.cleanup; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -19,6 +14,11 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + public class CleanupSingleAction extends SimpleCommand { private final CliPreferences preferences; @@ -30,7 +30,12 @@ public class CleanupSingleAction extends SimpleCommand { 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; @@ -44,44 +49,60 @@ public CleanupSingleAction(BibEntry entry, CliPreferences preferences, DialogSer public void execute() { isCanceled = false; - 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?"), - Localization.lang("Autogenerate PDF Names"), - Localization.lang("Cancel"), - Localization.lang("Do not ask again"), - optOut -> preferences.getAutoLinkPreferences().setAskAutoNamingPdfs(!optOut)); - if (!confirmed) { - isCanceled = true; - return; - } - } - - preferences.getCleanupPreferences().setActiveJobs(preset.getActiveJobs()); - preferences.getCleanupPreferences().setFieldFormatterCleanups(preset.getFieldFormatterCleanups()); - - cleanup(stateManager.getActiveDatabase().get(), preset); - }); + 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?"), + Localization.lang("Autogenerate PDF Names"), + Localization.lang("Cancel"), + Localization.lang("Do not ask again"), + optOut -> + preferences + .getAutoLinkPreferences() + .setAskAutoNamingPdfs(!optOut)); + if (!confirmed) { + isCanceled = true; + return; + } + } + + preferences.getCleanupPreferences().setActiveJobs(preset.getActiveJobs()); + preferences + .getCleanupPreferences() + .setFieldFormatterCleanups(preset.getFieldFormatterCleanups()); + + cleanup(stateManager.getActiveDatabase().get(), preset); + }); } /** * 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); @@ -91,15 +112,16 @@ 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")); + private void cleanup( + BibDatabaseContext databaseContext, CleanupPreferences cleanupPreferences) { + // 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); + } } } diff --git a/src/main/java/org/jabref/gui/collab/ChangeScanner.java b/src/main/java/org/jabref/gui/collab/ChangeScanner.java index e840136c9f57..bcca90f5c6ff 100644 --- a/src/main/java/org/jabref/gui/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/gui/collab/ChangeScanner.java @@ -1,9 +1,5 @@ package org.jabref.gui.collab; -import java.io.IOException; -import java.util.Collections; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.logic.importer.ImportFormatPreferences; @@ -11,10 +7,13 @@ import org.jabref.logic.importer.ParserResult; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.DummyFileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + public class ChangeScanner { private static final Logger LOGGER = LoggerFactory.getLogger(ChangeScanner.class); @@ -23,12 +22,12 @@ public class ChangeScanner { 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); + this.databaseChangeResolverFactory = + new DatabaseChangeResolverFactory(dialogService, database, preferences); } public List scanForChanges() { @@ -39,11 +38,17 @@ public List scanForChanges() { try { // Parse the modified file // Important: apply all post-load actions - ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences(); - ParserResult result = OpenDatabase.loadDatabase(database.getDatabasePath().get(), importFormatPreferences, new DummyFileUpdateMonitor()); + ImportFormatPreferences importFormatPreferences = + preferences.getImportFormatPreferences(); + ParserResult result = + OpenDatabase.loadDatabase( + database.getDatabasePath().get(), + importFormatPreferences, + new DummyFileUpdateMonitor()); BibDatabaseContext databaseOnDisk = result.getDatabaseContext(); - return DatabaseChangeList.compareAndGetChanges(database, databaseOnDisk, databaseChangeResolverFactory); + return DatabaseChangeList.compareAndGetChanges( + database, databaseOnDisk, databaseChangeResolverFactory); } catch (IOException e) { LOGGER.warn("Error while parsing changed file.", e); return Collections.emptyList(); diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChange.java b/src/main/java/org/jabref/gui/collab/DatabaseChange.java index 0ef63184c371..a4458b8d4c57 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChange.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChange.java @@ -1,7 +1,5 @@ package org.jabref.gui.collab; -import java.util.Optional; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -21,13 +19,28 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.model.database.BibDatabaseContext; -public sealed abstract class DatabaseChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename { +import java.util.Optional; + +public abstract sealed 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!"); diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java index f1160582e673..224958a8b671 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsView.java @@ -13,16 +13,16 @@ 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 abstract sealed class DatabaseChangeDetailsView extends AnchorPane + permits EntryWithPreviewAndSourceDetailsView, + GroupChangeDetailsView, + MetadataChangeDetailsView, + PreambleChangeDetailsView, + BibTexStringAddDetailsView, + BibTexStringChangeDetailsView, + BibTexStringDeleteDetailsView, + BibTexStringRenameDetailsView, + EntryChangeDetailsView { private static Double ANCHOR_PANE_OFFSET = 8D; diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java index 39939bce2636..4474fe3e8df6 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeDetailsViewFactory.java @@ -36,13 +36,14 @@ public class DatabaseChangeDetailsViewFactory { 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,39 +55,39 @@ 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/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java index be4969d1e52d..0b51cc5a94dd 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeList.java @@ -1,9 +1,5 @@ package org.jabref.gui.collab; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.jabref.gui.collab.entryadd.EntryAdd; import org.jabref.gui.collab.entrychange.EntryChange; import org.jabref.gui.collab.entrydelete.EntryDelete; @@ -19,9 +15,12 @@ import org.jabref.logic.bibtex.comparator.BibStringDiff; import org.jabref.model.database.BibDatabaseContext; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class DatabaseChangeList { - private DatabaseChangeList() { - } + private DatabaseChangeList() {} /** * Compares the given two databases, and returns the list of changes required to change the {@code originalDatabase} into the {@code otherDatabase} @@ -30,49 +29,107 @@ private DatabaseChangeList() { * @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 - ))); - }); - 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 + .getMetaDataDifferences() + .ifPresent( + diff -> { + changes.add( + new MetadataChange( + diff, 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))); 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); + return new BibTexStringAdd( + diff.getNewString(), originalDatabase, databaseChangeResolverFactory); } if (diff.getNewString() == null) { - return new BibTexStringDelete(diff.getOriginalString(), originalDatabase, databaseChangeResolverFactory); + return new BibTexStringDelete( + diff.getOriginalString(), originalDatabase, databaseChangeResolverFactory); } 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); } if (diff.newEntry() == null) { - return new EntryDelete(diff.originalEntry(), originalDatabase, databaseChangeResolverFactory); + return new EntryDelete( + diff.originalEntry(), originalDatabase, databaseChangeResolverFactory); } - return new EntryChange(diff.originalEntry(), diff.newEntry(), originalDatabase, databaseChangeResolverFactory); + return new EntryChange( + diff.originalEntry(), + diff.newEntry(), + originalDatabase, + databaseChangeResolverFactory); } } diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java index a9db70abd51a..ec38a29d01e8 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeMonitor.java @@ -1,13 +1,8 @@ package org.jabref.gui.collab; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.undo.UndoManager; - import javafx.util.Duration; +import org.controlsfx.control.action.Action; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -20,11 +15,15 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateListener; import org.jabref.model.util.FileUpdateMonitor; - -import org.controlsfx.control.action.Action; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.undo.UndoManager; + public class DatabaseChangeMonitor implements FileUpdateListener { private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseChangeMonitor.class); @@ -40,14 +39,15 @@ public class DatabaseChangeMonitor implements FileUpdateListener { 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; @@ -59,57 +59,81 @@ public DatabaseChangeMonitor(BibDatabaseContext database, this.listeners = new ArrayList<>(); - this.database.getDatabasePath().ifPresent(path -> { - try { - fileMonitor.addListenerForFile(path, this); - } catch (IOException e) { - LOGGER.error("Error while trying to monitor {}", path, e); - } - }); + this.database + .getDatabasePath() + .ifPresent( + path -> { + try { + fileMonitor.addListenerForFile(path, this); + } catch (IOException e) { + LOGGER.error("Error while trying to monitor {}", path, e); + } + }); addListener(this::notifyOnChange); } private void notifyOnChange(List changes) { - // The changes come from {@link org.jabref.gui.collab.DatabaseChangeList.compareAndGetChanges} + // 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"), event -> notificationPane.hide()), - new Action(Localization.lang("Review changes"), event -> { - DatabaseChangesResolverDialog databaseChangesResolverDialog = new DatabaseChangesResolverDialog(changes, database, Localization.lang("External Changes Resolver")); - var 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)); - 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 - saveState.resetChangedProperties(); - } else { - saveState.markBaseChanged(); - } - } - notificationPane.hide(); - })), + List.of( + new Action( + Localization.lang("Dismiss changes"), + event -> notificationPane.hide()), + new Action( + Localization.lang("Review changes"), + event -> { + DatabaseChangesResolverDialog databaseChangesResolverDialog = + new DatabaseChangesResolverDialog( + changes, + database, + Localization.lang("External Changes Resolver")); + var 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)); + 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 + saveState.resetChangedProperties(); + } else { + saveState.markBaseChanged(); + } + } + notificationPane.hide(); + })), Duration.ZERO); } @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); + .onSuccess( + changes -> { + if (!changes.isEmpty()) { + listeners.forEach( + listener -> listener.databaseChanged(changes)); + } + }) + .onFailure(e -> LOGGER.error("Error while watching for changes", e)) + .executeWith(taskExecutor); } } diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java index 83c318d49653..963094fcefcf 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeResolver.java @@ -1,11 +1,11 @@ package org.jabref.gui.collab; -import java.util.Optional; - import org.jabref.gui.DialogService; import org.jabref.gui.collab.entrychange.EntryChangeResolver; -public sealed abstract class DatabaseChangeResolver permits EntryChangeResolver { +import java.util.Optional; + +public abstract sealed class DatabaseChangeResolver permits EntryChangeResolver { protected final DialogService dialogService; protected DatabaseChangeResolver(DialogService dialogService) { diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java b/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java index 401d119d8eaa..045386d8439f 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangeResolverFactory.java @@ -1,19 +1,22 @@ package org.jabref.gui.collab; -import java.util.Optional; - import org.jabref.gui.DialogService; import org.jabref.gui.collab.entrychange.EntryChange; import org.jabref.gui.collab.entrychange.EntryChangeResolver; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.model.database.BibDatabaseContext; +import java.util.Optional; + 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; @@ -21,7 +24,9 @@ public DatabaseChangeResolverFactory(DialogService dialogService, BibDatabaseCon public Optional create(DatabaseChange change) { if (change instanceof EntryChange entryChange) { - return Optional.of(new EntryChangeResolver(entryChange, dialogService, databaseContext, preferences)); + return Optional.of( + new EntryChangeResolver( + entryChange, dialogService, databaseContext, preferences)); } return Optional.empty(); diff --git a/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java b/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java index 7c86ee9950bf..1d3ce461dcfe 100644 --- a/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java +++ b/src/main/java/org/jabref/gui/collab/DatabaseChangesResolverDialog.java @@ -1,10 +1,9 @@ package org.jabref.gui.collab; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXML; @@ -22,30 +21,31 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; - -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.undo.UndoManager; + public class DatabaseChangesResolverDialog extends BaseDialog { - private final static Logger LOGGER = LoggerFactory.getLogger(DatabaseChangesResolverDialog.class); + private static final 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}. */ - private final Map DETAILS_VIEW_CACHE = new HashMap<>(); + private final Map DETAILS_VIEW_CACHE = + new HashMap<>(); - @FXML - private TableView changesTableView; - @FXML - private TableColumn changeName; - @FXML - private Button askUserToResolveChangeButton; - @FXML - private BorderPane changeInfoPane; + @FXML private TableView changesTableView; + @FXML private TableColumn changeName; + @FXML private Button askUserToResolveChangeButton; + @FXML private BorderPane changeInfoPane; private final List changes; private final BibDatabaseContext database; @@ -69,24 +69,24 @@ public class DatabaseChangesResolverDialog extends BaseDialog { * @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); - - this.setResultConverter(button -> { - if (viewModel.areAllChangesResolved()) { - LOGGER.info("External changes are resolved successfully"); - return true; - } else { - LOGGER.info("External changes aren't resolved"); - return false; - } - }); + ViewLoader.view(this).load().setAsDialogPane(this); + + this.setResultConverter( + button -> { + if (viewModel.areAllChangesResolved()) { + LOGGER.info("External changes are resolved successfully"); + return true; + } else { + LOGGER.info("External changes aren't resolved"); + return false; + } + }); } public boolean areAllChangesAccepted() { @@ -99,34 +99,53 @@ public boolean areAllChangesDenied() { @FXML private void initialize() { - PreviewViewer previewViewer = new PreviewViewer(database, dialogService, preferences, themeManager, taskExecutor); - DatabaseChangeDetailsViewFactory databaseChangeDetailsViewFactory = new DatabaseChangeDetailsViewFactory(database, dialogService, themeManager, preferences, entryTypesManager, previewViewer, taskExecutor); + PreviewViewer previewViewer = + new PreviewViewer(database, dialogService, preferences, themeManager, taskExecutor); + DatabaseChangeDetailsViewFactory databaseChangeDetailsViewFactory = + new DatabaseChangeDetailsViewFactory( + database, + dialogService, + themeManager, + preferences, + entryTypesManager, + previewViewer, + taskExecutor); viewModel = new ExternalChangesResolverViewModel(changes, undoManager); changeName.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getName())); - askUserToResolveChangeButton.disableProperty().bind(viewModel.canAskUserToResolveChangeProperty().not()); + askUserToResolveChangeButton + .disableProperty() + .bind(viewModel.canAskUserToResolveChangeProperty().not()); changesTableView.setItems(viewModel.getVisibleChanges()); // Think twice before setting this to MULTIPLE... changesTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); changesTableView.getSelectionModel().selectFirst(); - viewModel.selectedChangeProperty().bind(changesTableView.getSelectionModel().selectedItemProperty()); - EasyBind.subscribe(viewModel.selectedChangeProperty(), selectedChange -> { - if (selectedChange != null) { - DatabaseChangeDetailsView detailsView = DETAILS_VIEW_CACHE.computeIfAbsent(selectedChange, databaseChangeDetailsViewFactory::create); - changeInfoPane.setCenter(detailsView); - } - }); - - EasyBind.subscribe(viewModel.areAllChangesResolvedProperty(), isResolved -> { - if (isResolved) { - areAllChangesAccepted = viewModel.areAllChangesAccepted(); - areAllChangesDenied = viewModel.areAllChangesDenied(); - close(); - } - }); + viewModel + .selectedChangeProperty() + .bind(changesTableView.getSelectionModel().selectedItemProperty()); + EasyBind.subscribe( + viewModel.selectedChangeProperty(), + selectedChange -> { + if (selectedChange != null) { + DatabaseChangeDetailsView detailsView = + DETAILS_VIEW_CACHE.computeIfAbsent( + selectedChange, databaseChangeDetailsViewFactory::create); + changeInfoPane.setCenter(detailsView); + } + }); + + EasyBind.subscribe( + viewModel.areAllChangesResolvedProperty(), + isResolved -> { + if (isResolved) { + areAllChangesAccepted = viewModel.areAllChangesAccepted(); + areAllChangesDenied = viewModel.areAllChangesDenied(); + close(); + } + }); } @FXML @@ -141,7 +160,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/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java index 1970921b3ddd..aae0df7d6370 100644 --- a/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java +++ b/src/main/java/org/jabref/gui/collab/ExternalChangesResolverViewModel.java @@ -1,11 +1,5 @@ package org.jabref.gui.collab; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ObjectProperty; @@ -14,15 +8,22 @@ import javafx.collections.ObservableList; import org.jabref.gui.AbstractViewModel; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + public class ExternalChangesResolverViewModel extends AbstractViewModel { - private final static Logger LOGGER = LoggerFactory.getLogger(ExternalChangesResolverViewModel.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(ExternalChangesResolverViewModel.class); - private final ObservableList visibleChanges = FXCollections.observableArrayList(); + 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 @@ -42,7 +43,8 @@ public class ExternalChangesResolverViewModel extends AbstractViewModel { private final UndoManager undoManager; - public ExternalChangesResolverViewModel(List externalChanges, UndoManager undoManager) { + public ExternalChangesResolverViewModel( + List externalChanges, UndoManager undoManager) { Objects.requireNonNull(externalChanges); assert !externalChanges.isEmpty(); @@ -50,10 +52,23 @@ public ExternalChangesResolverViewModel(List externalChanges, Un this.changes.addAll(externalChanges); 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); + 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); } public ObservableList getVisibleChanges() { @@ -97,10 +112,12 @@ public BooleanBinding canAskUserToResolveChangeProperty() { } public void acceptChange() { - getSelectedChange().ifPresent(selectedChange -> { - selectedChange.accept(); - getVisibleChanges().remove(selectedChange); - }); + getSelectedChange() + .ifPresent( + selectedChange -> { + selectedChange.accept(); + getVisibleChanges().remove(selectedChange); + }); } public void denyChange() { @@ -110,11 +127,13 @@ public void denyChange() { public void acceptMergedChange(DatabaseChange databaseChange) { Objects.requireNonNull(databaseChange); - getSelectedChange().ifPresent(oldChange -> { - changes.remove(oldChange); - changes.add(databaseChange); - databaseChange.accept(); - getVisibleChanges().remove(oldChange); - }); + getSelectedChange() + .ifPresent( + oldChange -> { + changes.remove(oldChange); + changes.add(databaseChange); + databaseChange.accept(); + getVisibleChanges().remove(oldChange); + }); } } diff --git a/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java b/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java index abf8ae633bfc..c105a07b79e9 100644 --- a/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java +++ b/src/main/java/org/jabref/gui/collab/entryadd/EntryAdd.java @@ -11,12 +11,17 @@ 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"))); + setChangeName( + addedEntry + .getCitationKey() + .map(key -> Localization.lang("Added entry '%0'", key)) + .orElse(Localization.lang("Added entry"))); } @Override diff --git a/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java b/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java index 44189beef936..bfb880f3eeb7 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/EntryChange.java @@ -1,7 +1,5 @@ package org.jabref.gui.collab.entrychange; -import javax.swing.undo.CompoundEdit; - import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.collab.DatabaseChangeResolverFactory; import org.jabref.gui.undo.NamedCompound; @@ -11,16 +9,24 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import javax.swing.undo.CompoundEdit; + 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) { diff --git a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java index 8cdabe824ef8..06bd8a9b67f2 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeDetailsView.java @@ -1,5 +1,7 @@ package org.jabref.gui.collab.entrychange; +import com.tobiasdiez.easybind.EasyBind; + import javafx.event.Event; import javafx.geometry.Orientation; import javafx.scene.control.Label; @@ -20,27 +22,28 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; -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")); onDisk.getStyleClass().add("lib-change-header"); // we need a copy here as we otherwise would set the same entry twice - PreviewViewer previewClone = new PreviewViewer(databaseContext, dialogService, preferences, themeManager, taskExecutor); + PreviewViewer previewClone = + new PreviewViewer( + databaseContext, dialogService, preferences, themeManager, taskExecutor); // The scroll bar used is not part of ScrollPane, but the attached WebView. WebView previewCloneView = (WebView) previewClone.getContent(); @@ -50,19 +53,38 @@ 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()) { - oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); - } - }); + EasyBind.subscribe( + newEntryTabPane.getSelectionModel().selectedIndexProperty(), + selectedIndex -> { + if (oldEntryTabPane.getSelectionModel().getSelectedIndex() + != selectedIndex.intValue()) { + oldEntryTabPane.getSelectionModel().select(selectedIndex.intValue()); + } + }); VBox containerOld = new VBox(inJabRef, oldEntryTabPane); VBox containerNew = new VBox(onDisk, newEntryTabPane); @@ -77,19 +99,25 @@ public EntryChangeDetailsView(BibEntry oldEntry, // https://stackoverflow.com/questions/49509395/synchronize-scrollbars-of-two-javafx-webviews // https://stackoverflow.com/questions/31264847/how-to-set-remember-scrollbar-thumb-position-in-javafx-8-webview private void synchronizeScrolling(WebView webView, WebView otherWebView) { - webView.addEventHandler(Event.ANY, event -> { - if (!scrolling) { - scrolling = true; - if (event instanceof MouseEvent mouseEvent) { - if (mouseEvent.isPrimaryButtonDown()) { - int value = (Integer) webView.getEngine().executeScript("window.scrollY"); - otherWebView.getEngine().executeScript("window.scrollTo(0, " + value + ")"); + webView.addEventHandler( + Event.ANY, + event -> { + if (!scrolling) { + scrolling = true; + if (event instanceof MouseEvent mouseEvent) { + if (mouseEvent.isPrimaryButtonDown()) { + int value = + (Integer) + webView.getEngine().executeScript("window.scrollY"); + otherWebView + .getEngine() + .executeScript("window.scrollTo(0, " + value + ")"); + } + } else { + otherWebView.fireEvent(event.copyFor(otherWebView, otherWebView)); + } + scrolling = false; } - } else { - otherWebView.fireEvent(event.copyFor(otherWebView, otherWebView)); - } - scrolling = false; - } - }); + }); } } diff --git a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java index 82afd0eced13..e80012b49af9 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/EntryChangeResolver.java @@ -1,7 +1,5 @@ package org.jabref.gui.collab.entrychange; -import java.util.Optional; - import org.jabref.gui.DialogService; import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.collab.DatabaseChangeResolver; @@ -14,13 +12,19 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; +import java.util.Optional; + 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 +33,21 @@ 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 - ); + entryChange.getOldEntry(), entriesMergeResult.mergedEntry(), databaseContext); } } diff --git a/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java b/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java index a4720a5f29b3..c46542a013e0 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/EntryWithPreviewAndSourceDetailsView.java @@ -13,8 +13,15 @@ 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); diff --git a/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java b/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java index 385d9d73ddd7..e6ad370f723c 100644 --- a/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java +++ b/src/main/java/org/jabref/gui/collab/entrychange/PreviewWithSourceTab.java @@ -1,11 +1,9 @@ package org.jabref.gui.collab.entrychange; -import java.io.IOException; -import java.io.StringWriter; - import javafx.scene.control.Tab; import javafx.scene.control.TabPane; +import org.fxmisc.richtext.CodeArea; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.preview.PreviewViewer; import org.jabref.logic.bibtex.BibEntryWriter; @@ -19,20 +17,33 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.strings.StringUtil; - -import org.fxmisc.richtext.CodeArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.StringWriter; + 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) { - return getPreviewWithSourceTab(entry, bibDatabaseContext, preferences, entryTypesManager, 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); @@ -52,18 +63,32 @@ public TabPane getPreviewWithSourceTab(BibEntry entry, BibDatabaseContext bibDat } try { - codeArea.appendText(getSourceString(entry, bibDatabaseContext.getMode(), preferences.getFieldPreferences(), entryTypesManager)); + 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); diff --git a/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java b/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java index 36116bb5845c..ab984bab82cc 100644 --- a/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java +++ b/src/main/java/org/jabref/gui/collab/entrydelete/EntryDelete.java @@ -11,12 +11,17 @@ 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"))); + setChangeName( + deletedEntry + .getCitationKey() + .map(key -> Localization.lang("Deleted entry '%0'", key)) + .orElse(Localization.lang("Deleted entry"))); } @Override diff --git a/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java b/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java index 4444d0f79c3b..8a29bf1a259b 100644 --- a/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java +++ b/src/main/java/org/jabref/gui/collab/groupchange/GroupChange.java @@ -14,11 +14,16 @@ 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 @@ -26,15 +31,25 @@ public void applyChange(NamedCompound undoEdit) { GroupTreeNode oldRoot = groupDiff.getOriginalGroupRoot(); GroupTreeNode newRoot = groupDiff.getNewGroupRoot(); - GroupTreeNode root = databaseContext.getMetaData().getGroups().orElseGet(() -> { - GroupTreeNode groupTreeNode = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup()); - databaseContext.getMetaData().setGroups(groupTreeNode); - return groupTreeNode; - }); + GroupTreeNode root = + databaseContext + .getMetaData() + .getGroups() + .orElseGet( + () -> { + GroupTreeNode groupTreeNode = + new GroupTreeNode( + DefaultGroupsFactory.getAllEntriesGroup()); + databaseContext.getMetaData().setGroups(groupTreeNode); + return groupTreeNode; + }); - final UndoableModifySubtree undo = new UndoableModifySubtree( - new GroupTreeNodeViewModel(databaseContext.getMetaData().getGroups().orElse(null)), - new GroupTreeNodeViewModel(root), Localization.lang("Modified groups")); + final UndoableModifySubtree undo = + new UndoableModifySubtree( + new GroupTreeNodeViewModel( + databaseContext.getMetaData().getGroups().orElse(null)), + new GroupTreeNodeViewModel(root), + Localization.lang("Modified groups")); root.removeAllChildren(); if (newRoot == null) { // I think setting root to null is not possible diff --git a/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java index de428484f295..d8765cada76e 100644 --- a/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/groupchange/GroupChangeDetailsView.java @@ -12,7 +12,10 @@ public GroupChangeDetailsView(GroupChange groupChange) { 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()); + 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); diff --git a/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java b/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java index 571fe74b7528..d477597bab4a 100644 --- a/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java +++ b/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChange.java @@ -10,7 +10,10 @@ 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")); @@ -22,8 +25,13 @@ public void applyChange(NamedCompound undoEdit) { databaseContext.setMetaData(metaDataDiff.getNewMetaData()); // 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())); + metaDataDiff + .getGroupDifferences() + .ifPresent( + groupDiff -> + databaseContext + .getMetaData() + .setGroups(groupDiff.getOriginalGroupRoot())); } public MetaDataDiff getMetaDataDiff() { diff --git a/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java index 7a6da5205b92..097646b421b0 100644 --- a/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/metedatachange/MetadataChangeDetailsView.java @@ -11,14 +11,16 @@ 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:")); header.getStyleClass().add("sectionHeader"); container.getChildren().add(header); - for (MetaDataDiff.Difference diff : metadataChange.getMetaDataDiff().getDifferences(globalCitationKeyPatterns)) { + for (MetaDataDiff.Difference diff : + metadataChange.getMetaDataDiff().getDifferences(globalCitationKeyPatterns)) { container.getChildren().add(new Label(getDifferenceString(diff.differenceType()))); container.getChildren().add(new Label(diff.originalObject().toString())); container.getChildren().add(new Label(diff.newObject().toString())); @@ -31,30 +33,18 @@ public MetadataChangeDetailsView(MetadataChange metadataChange, GlobalCitationKe 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 GENERAL_FILE_DIRECTORY -> - Localization.lang("General 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 GENERAL_FILE_DIRECTORY -> Localization.lang("General file directory"); + case CONTENT_SELECTOR -> Localization.lang("Content selectors"); }; } } diff --git a/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java b/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java index 5789d2bbbccc..3817678c9410 100644 --- a/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java +++ b/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChange.java @@ -7,7 +7,6 @@ import org.jabref.logic.bibtex.comparator.PreambleDiff; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +15,10 @@ public final class PreambleChange extends DatabaseChange { 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,7 +28,11 @@ 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() { diff --git a/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java index c49182f1d2e3..536fa46f851a 100644 --- a/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/preamblechange/PreambleChangeDetailsView.java @@ -19,11 +19,23 @@ 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()))); + container + .getChildren() + .add( + new Label( + Localization.lang( + "Value set externally: %0", + preambleDiff.getNewPreamble()))); } else { container.getChildren().add(new Label(Localization.lang("Value cleared externally"))); } diff --git a/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java b/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java index 98c311ca6dce..ad7b598a434f 100644 --- a/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java +++ b/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAdd.java @@ -8,7 +8,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.KeyCollisionException; import org.jabref.model.entry.BibtexString; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +16,10 @@ public final class BibTexStringAdd extends DatabaseChange { 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())); @@ -29,7 +31,11 @@ public void applyChange(NamedCompound undoEdit) { databaseContext.getDatabase().addString(addedString); undoEdit.addEdit(new UndoableInsertString(databaseContext.getDatabase(), addedString)); } catch (KeyCollisionException ex) { - LOGGER.warn("Error: could not add string '{}': {}", addedString.getName(), ex.getMessage(), ex); + LOGGER.warn( + "Error: could not add string '{}': {}", + addedString.getName(), + ex.getMessage(), + ex); } } diff --git a/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java b/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java index ffca7b4d7ba6..5eb3cc135307 100644 --- a/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/stringadd/BibTexStringAddDetailsView.java @@ -12,11 +12,16 @@ 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/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java b/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java index 8cb58e99d46e..cd8959622365 100644 --- a/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java +++ b/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChange.java @@ -7,7 +7,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibtexString; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +16,11 @@ public final class BibTexStringChange extends DatabaseChange { 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; diff --git a/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java b/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java index 880f5661ad85..26283bb2a5ba 100644 --- a/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/stringchange/BibTexStringChangeDetailsView.java @@ -12,13 +12,24 @@ 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/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java b/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java index 2f55cf7d3317..61b3a3032c91 100644 --- a/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java +++ b/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDelete.java @@ -7,7 +7,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibtexString; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +15,10 @@ public final class BibTexStringDelete extends DatabaseChange { 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())); @@ -26,9 +28,14 @@ public BibTexStringDelete(BibtexString deletedString, BibDatabaseContext databas public void applyChange(NamedCompound undoEdit) { try { databaseContext.getDatabase().removeString(deletedString.getId()); - undoEdit.addEdit(new UndoableRemoveString(databaseContext.getDatabase(), deletedString)); + undoEdit.addEdit( + new UndoableRemoveString(databaseContext.getDatabase(), deletedString)); } catch (Exception ex) { - LOGGER.warn("Error: could not remove string '{}': {}", deletedString.getName(), ex.getMessage(), ex); + LOGGER.warn( + "Error: could not remove string '{}': {}", + deletedString.getName(), + ex.getMessage(), + ex); } } diff --git a/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java b/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java index 31a63e5504c3..e87a8d5936a7 100644 --- a/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/stringdelete/BibTexStringDeleteDetailsView.java @@ -12,11 +12,17 @@ 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/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java b/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java index 0236ebda467d..0ab2938fc1f1 100644 --- a/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java +++ b/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRename.java @@ -7,7 +7,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibtexString; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +16,11 @@ public final class BibTexStringRename extends DatabaseChange { 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,10 @@ 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(); diff --git a/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java b/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java index fa826b08e8f3..5ea7c0553dc8 100644 --- a/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java +++ b/src/main/java/org/jabref/gui/collab/stringrename/BibTexStringRenameDetailsView.java @@ -7,7 +7,11 @@ 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/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java b/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java index 1c234329b764..7e9c395a3bae 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanel.java @@ -1,6 +1,8 @@ package org.jabref.gui.commonfxcontrols; -import java.util.Collection; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -19,8 +21,7 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.types.EntryType; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.util.Collection; public class CitationKeyPatternsPanel extends TableView { @@ -38,14 +39,14 @@ public class CitationKeyPatternsPanel extends TableView() .withText(EntryType::getDisplayName) .install(entryTypeColumn); - this.setOnSort(event -> - viewModel.patternListProperty().sort(CitationKeyPatternsPanelViewModel.defaultOnTopComparator)); + this.setOnSort( + event -> + viewModel + .patternListProperty() + .sort(CitationKeyPatternsPanelViewModel.defaultOnTopComparator)); patternColumn.setSortable(true); patternColumn.setReorderable(false); @@ -72,10 +76,15 @@ private void initialize() { 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())) + .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()); @@ -83,7 +92,8 @@ private void initialize() { this.itemsProperty().bindBidirectional(viewModel.patternListProperty()); } - public void setValues(Collection entryTypeList, AbstractCitationKeyPatterns keyPattern) { + public void setValues( + Collection entryTypeList, AbstractCitationKeyPatterns keyPattern) { viewModel.setValues(entryTypeList, keyPattern); } @@ -112,8 +122,15 @@ private void jumpToSearchKey(KeyEvent keypressed) { 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 { @@ -124,7 +141,9 @@ public void updateItem(CitationKeyPatternsPanelItemModel item, boolean empty) { setStyle(""); } else if (isSelected()) { setStyle("-fx-background-color: -fx-selection-bar"); - } else if (item.getEntryType().getName().equals(CitationKeyPatternsPanelViewModel.ENTRY_TYPE_DEFAULT_NAME)) { + } else if (item.getEntryType() + .getName() + .equals(CitationKeyPatternsPanelViewModel.ENTRY_TYPE_DEFAULT_NAME)) { setStyle("-fx-background-color: -fx-default-button"); } else { setStyle(""); diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java b/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java index 86cd170d4ead..3f03302bbbc5 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelItemModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.commonfxcontrols; -import java.util.Objects; - import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -9,6 +7,8 @@ import org.jabref.model.entry.types.EntryType; +import java.util.Objects; + public class CitationKeyPatternsPanelItemModel { private final ObjectProperty entryType = new SimpleObjectProperty<>(); private final StringProperty pattern = new SimpleStringProperty(""); diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java b/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java index 7a0c98f4cd8e..cfa1baed4fca 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/CitationKeyPatternsPanelViewModel.java @@ -1,8 +1,5 @@ package org.jabref.gui.commonfxcontrols; -import java.util.Collection; -import java.util.Comparator; - import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleListProperty; @@ -16,27 +13,33 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.types.EntryType; +import java.util.Collection; +import java.util.Comparator; + public class CitationKeyPatternsPanelViewModel { public static final String ENTRY_TYPE_DEFAULT_NAME = "default"; - public static Comparator defaultOnTopComparator = (o1, o2) -> { - String itemOneName = o1.getEntryType().getName(); - String itemTwoName = o2.getEntryType().getName(); + public static Comparator defaultOnTopComparator = + (o1, o2) -> { + String itemOneName = o1.getEntryType().getName(); + String itemTwoName = o2.getEntryType().getName(); - if (itemOneName.equals(itemTwoName)) { - return 0; - } else if (itemOneName.equals(ENTRY_TYPE_DEFAULT_NAME)) { - return -1; - } else if (itemTwoName.equals(ENTRY_TYPE_DEFAULT_NAME)) { - return 1; - } + if (itemOneName.equals(itemTwoName)) { + return 0; + } else if (itemOneName.equals(ENTRY_TYPE_DEFAULT_NAME)) { + return -1; + } else if (itemTwoName.equals(ENTRY_TYPE_DEFAULT_NAME)) { + return 1; + } - return 0; - }; + return 0; + }; - private final ListProperty patternListProperty = new SimpleListProperty<>(); - private final ObjectProperty defaultItemProperty = new SimpleObjectProperty<>(); + private final ListProperty patternListProperty = + new SimpleListProperty<>(); + private final ObjectProperty defaultItemProperty = + new SimpleObjectProperty<>(); private final CitationKeyPatternPreferences keyPatternPreferences; @@ -44,29 +47,40 @@ public CitationKeyPatternsPanelViewModel(CitationKeyPatternPreferences keyPatter this.keyPatternPreferences = keyPatternPreferences; } - public void setValues(Collection entryTypeList, AbstractCitationKeyPatterns initialKeyPattern) { + 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 { defaultPattern = initialKeyPattern.getDefaultValue().stringRepresentation(); } - defaultItemProperty.setValue(new CitationKeyPatternsPanelItemModel(new DefaultEntryType(), defaultPattern)); + defaultItemProperty.setValue( + new CitationKeyPatternsPanelItemModel(new DefaultEntryType(), defaultPattern)); 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)); - }); + .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) { diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java b/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java index 287b2918aaca..a4887e2a2321 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanel.java @@ -1,5 +1,9 @@ package org.jabref.gui.commonfxcontrols; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ReadOnlyObjectWrapper; @@ -24,9 +28,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - public class FieldFormatterCleanupsPanel extends VBox { @FXML private CheckBox cleanupsEnabled; @@ -42,9 +43,7 @@ public class FieldFormatterCleanupsPanel extends VBox { private FieldFormatterCleanupsPanelViewModel viewModel; public FieldFormatterCleanupsPanel() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML @@ -61,30 +60,41 @@ private void setupTable() { // ToDo: To be editable the list needs a view model wrapper for FieldFormatterCleanup - fieldColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getField())); + fieldColumn.setCellValueFactory( + cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getField())); new ValueTableCellFactory() .withText(Field::getDisplayName) .install(fieldColumn); - formatterColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getFormatter())); + formatterColumn.setCellValueFactory( + cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getFormatter())); new ValueTableCellFactory() .withText(Formatter::getName) .install(formatterColumn); - actionsColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>(cellData.getValue().getField())); + 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())) + .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()); - cleanupsList.setOnKeyPressed(event -> { - if (event.getCode() == KeyCode.DELETE) { - viewModel.removeCleanup(cleanupsList.getSelectionModel().getSelectedItem()); - } - }); + cleanupsList.setOnKeyPressed( + event -> { + if (event.getCode() == KeyCode.DELETE) { + viewModel.removeCleanup(cleanupsList.getSelectionModel().getSelectedItem()); + } + }); } private void setupCombos() { @@ -92,27 +102,30 @@ private void setupCombos() { .withText(Field::getDisplayName) .install(addableFields); addableFields.setConverter(FieldsUtil.FIELD_STRING_CONVERTER); - addableFields.setOnKeyPressed(event -> { - if (event.getCode() == KeyCode.TAB || event.getCode() == KeyCode.ENTER) { - addableFormatters.requestFocus(); - event.consume(); - } - }); + addableFields.setOnKeyPressed( + event -> { + if (event.getCode() == KeyCode.TAB || event.getCode() == KeyCode.ENTER) { + addableFormatters.requestFocus(); + event.consume(); + } + }); new ViewModelListCellFactory() .withText(Formatter::getName) .withStringTooltip(Formatter::getDescription) .install(addableFormatters); - addableFormatters.setOnKeyPressed(event -> { - if (event.getCode() == KeyCode.ENTER) { - viewModel.addCleanup(); - event.consume(); - } - }); + addableFormatters.setOnKeyPressed( + event -> { + if (event.getCode() == KeyCode.ENTER) { + viewModel.addCleanup(); + event.consume(); + } + }); } private void setupBindings() { - BindingsHelper.bindBidirectional((ObservableValue) cleanupsEnabled.selectedProperty(), + BindingsHelper.bindBidirectional( + (ObservableValue) cleanupsEnabled.selectedProperty(), viewModel.cleanupsDisableProperty(), disabled -> cleanupsEnabled.selectedProperty().setValue(!disabled), selected -> viewModel.cleanupsDisableProperty().setValue(!selected)); diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java b/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java index d7a926291e1f..11c59294538c 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/FieldFormatterCleanupsPanelViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.commonfxcontrols; -import java.util.Comparator; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -21,15 +19,28 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; +import java.util.Comparator; + 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 ObjectProperty selectedFormatterProperty = new SimpleObjectProperty<>(); + 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; @@ -38,13 +49,18 @@ public FieldFormatterCleanupsPanelViewModel(StateManager stateManager) { } public void resetToRecommended() { - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - if (databaseContext.isBiblatexMode()) { - cleanupsListProperty.setAll(FieldFormatterCleanups.RECOMMEND_BIBLATEX_ACTIONS); - } else { - cleanupsListProperty.setAll(FieldFormatterCleanups.RECOMMEND_BIBTEX_ACTIONS); - } - }); + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> { + if (databaseContext.isBiblatexMode()) { + cleanupsListProperty.setAll( + FieldFormatterCleanups.RECOMMEND_BIBLATEX_ACTIONS); + } else { + cleanupsListProperty.setAll( + FieldFormatterCleanups.RECOMMEND_BIBTEX_ACTIONS); + } + }); } public void clearAll() { @@ -52,13 +68,14 @@ public void clearAll() { } public void addCleanup() { - if (selectedFieldProperty.getValue() == null || selectedFormatterProperty.getValue() == null) { + if (selectedFieldProperty.getValue() == null + || selectedFormatterProperty.getValue() == null) { return; } - FieldFormatterCleanup cleanup = new FieldFormatterCleanup( - selectedFieldProperty.getValue(), - selectedFormatterProperty.getValue()); + FieldFormatterCleanup cleanup = + new FieldFormatterCleanup( + selectedFieldProperty.getValue(), selectedFormatterProperty.getValue()); if (cleanupsListProperty.stream().noneMatch(item -> item.equals(cleanup))) { cleanupsListProperty.add(cleanup); diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java index 12dcd20777f0..a6c15c8bab86 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanel.java @@ -1,9 +1,8 @@ package org.jabref.gui.commonfxcontrols; -import java.util.List; -import java.util.stream.Collectors; +import com.airhacks.afterburner.views.ViewLoader; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; @@ -28,8 +27,10 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.util.List; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; public class SaveOrderConfigPanel extends VBox { @@ -45,47 +46,64 @@ public class SaveOrderConfigPanel extends VBox { private SaveOrderConfigPanelViewModel viewModel; public SaveOrderConfigPanel() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @FXML private void initialize() { viewModel = new SaveOrderConfigPanelViewModel(); - exportInOriginalOrder.selectedProperty().bindBidirectional(viewModel.saveInOriginalProperty()); - exportInTableOrder.selectedProperty().bindBidirectional(viewModel.saveInTableOrderProperty()); - exportInSpecifiedOrder.selectedProperty().bindBidirectional(viewModel.saveInSpecifiedOrderProperty()); - - viewModel.sortCriteriaProperty().addListener((ListChangeListener) change -> { - while (change.next()) { - if (change.wasReplaced()) { - 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()) { - for (SortCriterionViewModel criterionViewModel : change.getRemoved()) { - clearCriterionRow(change.getFrom()); - } - } - } - }); + exportInOriginalOrder + .selectedProperty() + .bindBidirectional(viewModel.saveInOriginalProperty()); + exportInTableOrder + .selectedProperty() + .bindBidirectional(viewModel.saveInTableOrderProperty()); + exportInSpecifiedOrder + .selectedProperty() + .bindBidirectional(viewModel.saveInSpecifiedOrderProperty()); + + viewModel + .sortCriteriaProperty() + .addListener( + (ListChangeListener) + change -> { + while (change.next()) { + if (change.wasReplaced()) { + 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()) { + for (SortCriterionViewModel criterionViewModel : + change.getRemoved()) { + clearCriterionRow(change.getFrom()); + } + } + } + }); } 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)); - } - }); + .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); @@ -134,28 +152,34 @@ 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)); - } - }); + .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 diff --git a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java index 6776df54e471..ea650e007144 100644 --- a/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java +++ b/src/main/java/org/jabref/gui/commonfxcontrols/SaveOrderConfigPanelViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.commonfxcontrols; -import java.util.Collections; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -10,17 +8,20 @@ import org.jabref.model.entry.field.Field; +import java.util.Collections; + 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() { - } + public SaveOrderConfigPanelViewModel() {} public void addCriterion() { selectedSortCriteriaProperty.add(new SortCriterionViewModel()); diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java index 582971b6cc74..140c93cdb853 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesAction.java @@ -1,8 +1,7 @@ package org.jabref.gui.copyfiles; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; import javafx.concurrent.Task; @@ -16,8 +15,9 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; public class CopyFilesAction extends SimpleCommand { @@ -26,10 +26,11 @@ public class CopyFilesAction extends SimpleCommand { 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,31 +41,42 @@ 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))); + dialogService.showCustomDialogAndWait( + new CopyFilesDialogView(new CopyFilesResultListDependency(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(); - Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); - exportPath.ifPresent(path -> { - Task> exportTask = new CopyFilesTask(database, entries, path, preferences); + DirectoryDialogConfiguration dirDialogConfiguration = + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory( + preferences.getExportPreferences().getExportWorkingDirectory()) + .build(); + Optional exportPath = + dialogService.showDirectorySelectionDialog(dirDialogConfiguration); + exportPath.ifPresent( + path -> { + 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())); - }); + uiTaskExecutor.execute(exportTask); + exportTask.setOnSucceeded(e -> showDialog(exportTask.getValue())); + }); } } diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java index 7b1bfcc2f58b..bca1ce0f08a5 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogView.java @@ -1,5 +1,7 @@ package org.jabref.gui.copyfiles; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.ButtonType; import javafx.scene.control.TableColumn; @@ -12,8 +14,6 @@ import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - public class CopyFilesDialogView extends BaseDialog { @FXML private TableView tvResult; @@ -29,9 +29,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,16 +42,22 @@ 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/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java index e30563b4fa9b..7b235943ae56 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesDialogViewModel.java @@ -7,8 +7,8 @@ public class CopyFilesDialogViewModel extends AbstractViewModel { - private final SimpleListProperty copyFilesResultItems = new SimpleListProperty<>( - FXCollections.observableArrayList()); + private final SimpleListProperty copyFilesResultItems = + new SimpleListProperty<>(FXCollections.observableArrayList()); public CopyFilesDialogViewModel(CopyFilesResultListDependency results) { copyFilesResultItems.addAll(results.getResults()); diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java index e48f06851126..de83460d2333 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesResultItemViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.copyfiles; -import java.nio.file.Path; - import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -10,10 +8,13 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; +import java.nio.file.Path; + public class CopyFilesResultItemViewModel { private final StringProperty file = new SimpleStringProperty(""); - private final ObjectProperty icon = new SimpleObjectProperty<>(IconTheme.JabRefIcons.WARNING); + private final ObjectProperty icon = + new SimpleObjectProperty<>(IconTheme.JabRefIcons.WARNING); private final StringProperty message = new SimpleStringProperty(""); public CopyFilesResultItemViewModel(Path file, boolean success, String message) { @@ -38,6 +39,10 @@ public ObjectProperty getIcon() { @Override public String toString() { - return "CopyFilesResultItemViewModel [file=" + file.get() + ", message=" + message.get() + "]"; + return "CopyFilesResultItemViewModel [file=" + + file.get() + + ", message=" + + message.get() + + "]"; } } diff --git a/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java b/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java index b92991a7a650..31ebd5c1f3e0 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopyFilesTask.java @@ -1,17 +1,5 @@ package org.jabref.gui.copyfiles; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.BiFunction; - import javafx.concurrent.Task; import org.jabref.logic.l10n.Localization; @@ -22,10 +10,21 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.util.OptionalUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; + public class CopyFilesTask extends Task> { private static final Logger LOGGER = LoggerFactory.getLogger(CopyFilesTask.class); @@ -35,7 +34,8 @@ public class CopyFilesTask extends Task> { 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<>(); @@ -43,9 +43,14 @@ public class CopyFilesTask extends Task> { private int numberSuccessful; private int totalFilesCounter; - private final BiFunction resolvePathFilename = (path, file) -> path.resolve(file.getFileName()); + 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 +67,10 @@ 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,13 +83,19 @@ 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); - Optional fileToExport = fileName.findIn(databaseContext, preferences.getFilePreferences()); + Optional fileToExport = + fileName.findIn(databaseContext, preferences.getFilePreferences()); - newPath = OptionalUtil.combine(Optional.of(exportPath), fileToExport, resolvePathFilename); + newPath = + OptionalUtil.combine( + Optional.of(exportPath), fileToExport, resolvePathFilename); if (newPath.isPresent()) { Path newFile = newPath.get(); @@ -110,10 +124,12 @@ 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), - newPath.map(Path::getParent).map(Path::toString).orElse("")); + String successMessage = + Localization.lang( + "Copied %0 files of %1 successfully to %2", + Integer.toString(numberSuccessful), + Integer.toString(totalFilesCounter), + newPath.map(Path::getParent).map(Path::toString).orElse("")); updateMessage(successMessage); bw.write(successMessage); return results; @@ -130,7 +146,8 @@ private void writeLogMessage(Path newFile, BufferedWriter bw, String logMessage) } private void addResultToList(Path newFile, boolean success, String logMessage) { - CopyFilesResultItemViewModel result = new CopyFilesResultItemViewModel(newFile, success, logMessage); + CopyFilesResultItemViewModel result = + new CopyFilesResultItemViewModel(newFile, success, logMessage); results.add(result); } } diff --git a/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java b/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java index 22ae70d77cd3..0c92e7434eeb 100644 --- a/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java +++ b/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAction.java @@ -1,9 +1,5 @@ package org.jabref.gui.copyfiles; -import java.nio.file.Path; -import java.util.Optional; -import java.util.function.BiFunction; - import javafx.beans.binding.Bindings; import org.jabref.gui.DialogService; @@ -16,6 +12,10 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.util.OptionalUtil; +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.BiFunction; + public class CopySingleFileAction extends SimpleCommand { private final LinkedFile linkedFile; @@ -23,43 +23,68 @@ public class CopySingleFileAction extends SimpleCommand { private final BibDatabaseContext databaseContext; private final FilePreferences filePreferences; - private final BiFunction resolvePathFilename = (path, file) -> path.resolve(file.getFileName()); + 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; this.filePreferences = filePreferences; - this.executable.bind(Bindings.createBooleanBinding( - () -> !linkedFile.isOnlineLink() - && linkedFile.findIn(databaseContext, this.filePreferences).isPresent(), - linkedFile.linkProperty())); + this.executable.bind( + Bindings.createBooleanBinding( + () -> + !linkedFile.isOnlineLink() + && linkedFile + .findIn(databaseContext, this.filePreferences) + .isPresent(), + linkedFile.linkProperty())); } @Override public void execute() { - DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(filePreferences.getWorkingDirectory()) - .build(); - Optional exportPath = dialogService.showDirectorySelectionDialog(dirDialogConfiguration); + DirectoryDialogConfiguration dirDialogConfiguration = + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); + Optional exportPath = + dialogService.showDirectorySelectionDialog(dirDialogConfiguration); exportPath.ifPresent(this::copyFileToDestination); } private void copyFileToDestination(Path exportPath) { Optional fileToExport = linkedFile.findIn(databaseContext, filePreferences); - Optional newPath = OptionalUtil.combine(Optional.of(exportPath), fileToExport, resolvePathFilename); + Optional newPath = + OptionalUtil.combine(Optional.of(exportPath), fileToExport, resolvePathFilename); if (newPath.isPresent()) { Path newFile = newPath.get(); - boolean success = fileToExport.isPresent() && FileUtil.copyFile(fileToExport.get(), newFile, false); + 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(""))); + 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.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(""))); + dialogService.showErrorDialogAndWait( + Localization.lang( + "Could not resolve the file %0", + fileToExport.map(Path::getParent).map(Path::toString).orElse(""))); } } } diff --git a/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java b/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java index 449f89adc29e..c3e4b619da0a 100644 --- a/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/os/DefaultDesktop.java @@ -1,18 +1,17 @@ package org.jabref.gui.desktop.os; -import java.awt.Desktop; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; - import org.jabref.Launcher; import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.DialogService; import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.logic.util.Directories; - import org.slf4j.LoggerFactory; +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + /** * 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 Launcher#addLogToDisk} @@ -23,7 +22,11 @@ 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(new File(filePath)); } @@ -40,7 +43,8 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { @Override public void openConsole(String absolutePath, DialogService dialogService) throws IOException { - LoggerFactory.getLogger(DefaultDesktop.class).error("This feature is not supported by your Operating System."); + LoggerFactory.getLogger(DefaultDesktop.class) + .error("This feature is not supported by your Operating System."); } @Override diff --git a/src/main/java/org/jabref/gui/desktop/os/Linux.java b/src/main/java/org/jabref/gui/desktop/os/Linux.java index b083212bb7bc..503683f426d9 100644 --- a/src/main/java/org/jabref/gui/desktop/os/Linux.java +++ b/src/main/java/org/jabref/gui/desktop/os/Linux.java @@ -1,17 +1,5 @@ package org.jabref.gui.desktop.os; -import java.awt.Desktop; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Locale; -import java.util.Optional; - import org.jabref.Launcher; import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.DialogService; @@ -22,9 +10,20 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.Directories; import org.jabref.logic.util.HeadlessExecutorService; - import org.slf4j.LoggerFactory; +import java.awt.Desktop; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + /** * 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 Launcher#addLogToDisk} @@ -34,39 +33,54 @@ @AllowedToUseAwt("Requires AWT to open a file with the native method") public class Linux extends NativeDesktop { - private static final String ETC_ALTERNATIVES_X_TERMINAL_EMULATOR = "/etc/alternatives/x-terminal-emulator"; + private static final String ETC_ALTERNATIVES_X_TERMINAL_EMULATOR = + "/etc/alternatives/x-terminal-emulator"; private void nativeOpenFile(String filePath) { - HeadlessExecutorService.INSTANCE.execute(() -> { - try { - File file = new File(filePath); - Desktop.getDesktop().open(file); - LoggerFactory.getLogger(Linux.class).debug("Open file in default application with Desktop integration"); - } catch (IllegalArgumentException e) { - LoggerFactory.getLogger(Linux.class).debug("Fail back to xdg-open"); - try { - String[] cmd = {"xdg-open", filePath}; - Runtime.getRuntime().exec(cmd); - } catch (Exception e2) { - LoggerFactory.getLogger(Linux.class).warn("Open operation not successful: ", e2); - } - } catch (IOException e) { - LoggerFactory.getLogger(Linux.class).warn("Native open operation not successful: ", e); - } - }); + HeadlessExecutorService.INSTANCE.execute( + () -> { + try { + File file = new File(filePath); + Desktop.getDesktop().open(file); + LoggerFactory.getLogger(Linux.class) + .debug("Open file in default application with Desktop integration"); + } catch (IllegalArgumentException e) { + LoggerFactory.getLogger(Linux.class).debug("Fail back to xdg-open"); + try { + String[] cmd = {"xdg-open", filePath}; + Runtime.getRuntime().exec(cmd); + } catch (Exception e2) { + LoggerFactory.getLogger(Linux.class) + .warn("Open operation not successful: ", e2); + } + } 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); @@ -88,8 +102,12 @@ 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); @@ -103,7 +121,9 @@ 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); @@ -114,17 +134,26 @@ public void openFolderAndSelectFile(Path filePath) throws IOException { } 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 + 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)); + 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); @@ -134,14 +163,19 @@ 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; } - ProcessBuilder processBuilder = new ProcessBuilder("readlink", ETC_ALTERNATIVES_X_TERMINAL_EMULATOR); + ProcessBuilder processBuilder = + new ProcessBuilder("readlink", ETC_ALTERNATIVES_X_TERMINAL_EMULATOR); Process process = processBuilder.start(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream()))) { String emulatorName = reader.readLine(); if (emulatorName != null) { emulatorName = emulatorName.substring(emulatorName.lastIndexOf(File.separator) + 1); @@ -158,14 +192,21 @@ public void openConsole(String absolutePath, DialogService dialogService) throws cmd = new String[] {emulatorName, absolutePath}; } - LoggerFactory.getLogger(Linux.class).debug("Opening terminal using {}", String.join(" ", cmd)); + LoggerFactory.getLogger(Linux.class) + .debug("Opening terminal using {}", String.join(" ", cmd)); ProcessBuilder builder = new ProcessBuilder(cmd); builder.directory(new File(absolutePath)); 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); @@ -188,9 +229,15 @@ 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(); @@ -198,7 +245,10 @@ 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); diff --git a/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java b/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java index e68b255d9953..92bff4aa698a 100644 --- a/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java +++ b/src/main/java/org/jabref/gui/desktop/os/NativeDesktop.java @@ -1,16 +1,10 @@ package org.jabref.gui.desktop.os; -import java.awt.Desktop; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; -import java.util.regex.Pattern; +import static org.jabref.model.entry.field.StandardField.PDF; +import static org.jabref.model.entry.field.StandardField.PS; +import static org.jabref.model.entry.field.StandardField.URL; + +import com.airhacks.afterburner.injection.Injector; import org.jabref.Launcher; import org.jabref.architecture.AllowedToUseAwt; @@ -32,13 +26,19 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.entry.identifier.Identifier; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.LoggerFactory; -import static org.jabref.model.entry.field.StandardField.PDF; -import static org.jabref.model.entry.field.StandardField.PS; -import static org.jabref.model.entry.field.StandardField.URL; +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; /** * This class contains bundles OS specific implementations for file directories and file/application open handling methods. @@ -56,7 +56,8 @@ @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]+://.*"); @@ -65,18 +66,20 @@ public abstract class NativeDesktop { *

* 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) + 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)) { // Find the default directory for this field type: - List directories = databaseContext.getFileDirectories(preferences.getFilePreferences()); + List directories = + databaseContext.getFileDirectories(preferences.getFilePreferences()); Optional file = FileUtil.find(link, directories); @@ -92,7 +95,8 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, if ("pdf".equalsIgnoreCase(split[split.length - 1])) { field = PDF; } else if ("ps".equalsIgnoreCase(split[split.length - 1]) - || ((split.length >= 3) && "ps".equalsIgnoreCase(split[split.length - 2]))) { + || ((split.length >= 3) + && "ps".equalsIgnoreCase(split[split.length - 2]))) { field = PS; } } @@ -105,18 +109,25 @@ public static void openExternalViewer(BibDatabaseContext databaseContext, } else if (StandardField.EPRINT == field) { IdentifierParser identifierParser = new IdentifierParser(entry); - link = identifierParser.parse(StandardField.EPRINT) - .flatMap(Identifier::getExternalURI) - .map(URI::toASCIIString) - .orElse(link); + link = + identifierParser + .parse(StandardField.EPRINT) + .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")); + 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 verify that the eprint field has a valid '%0' id", + link)); } } // should be opened in browser @@ -124,24 +135,33 @@ 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()); + get().openFile( + link, + PS.getName(), + preferences.getExternalApplicationsPreferences()); } catch (IOException e) { - LoggerFactory.getLogger(NativeDesktop.class).error("An error occurred on the command: {}", link, e); + LoggerFactory.getLogger(NativeDesktop.class) + .error("An error occurred on the command: {}", link, e); } } case PDF -> { try { - get().openFile(link, PDF.getName(), preferences.getExternalApplicationsPreferences()); + get().openFile( + link, + PDF.getName(), + preferences.getExternalApplicationsPreferences()); } catch (IOException e) { - LoggerFactory.getLogger(NativeDesktop.class).error("An error occurred on the command: {}", link, 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"); + LoggerFactory.getLogger(NativeDesktop.class) + .info( + "Message: currently only PDF, PS and HTML files can be opened by double clicking"); } } @@ -150,16 +170,22 @@ private static void openDoi(String doi, GuiPreferences preferences) throws IOExc openBrowser(link, preferences.getExternalApplicationsPreferences()); } - public static void openCustomDoi(String link, GuiPreferences preferences, DialogService dialogService) { + 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 { @@ -175,11 +201,13 @@ private static void openIsbn(String isbn, GuiPreferences preferences) throws IOE * @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 { + public static boolean openExternalFileAnyFormat( + final BibDatabaseContext databaseContext, + 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; @@ -197,15 +225,19 @@ public static boolean openExternalFileAnyFormat(final BibDatabaseContext databas return true; } - private static void openExternalFilePlatformIndependent(Optional fileType, - String filePath, - ExternalApplicationsPreferences externalApplicationsPreferences) + 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); + get().openFile( + filePath, + fileType.get().getExtension(), + externalApplicationsPreferences); } else { get().openFileWithApplication(filePath, application); } @@ -222,9 +254,11 @@ 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); } @@ -309,18 +357,25 @@ public static void openBrowser(URI url, ExternalApplicationsPreferences external * * @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) { - ClipBoardManager clipBoardManager = Injector.instantiateModelOrService(ClipBoardManager.class); + ClipBoardManager clipBoardManager = + Injector.instantiateModelOrService(ClipBoardManager.class); clipBoardManager.setContent(url); LoggerFactory.getLogger(NativeDesktop.class).error("Could not open browser", exception); String couldNotOpenBrowser = Localization.lang("Could not open browser."); String openManually = Localization.lang("Please open %0 manually.", url); - String copiedToClipboard = Localization.lang("The link has been copied to the clipboard."); + 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); } } @@ -335,7 +390,11 @@ public static NativeDesktop get() { 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. @@ -343,11 +402,13 @@ public static NativeDesktop get() { * @param filePath The filename. * @param application Link to the app that opens the file. */ - public abstract void openFileWithApplication(String filePath, String application) throws IOException; + public abstract void openFileWithApplication(String filePath, String application) + throws IOException; public abstract void openFolderAndSelectFile(Path file) throws IOException; - public abstract void openConsole(String absolutePath, DialogService dialogService) throws IOException; + public abstract void openConsole(String absolutePath, DialogService dialogService) + throws IOException; /** * Returns the path to the system's applications folder. @@ -362,13 +423,13 @@ public static NativeDesktop get() { * @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. @@ -379,7 +440,8 @@ public Path getDefaultFileChooserDirectory() { public void moveToTrash(Path path) { boolean success = Desktop.getDesktop().moveToTrash(path.toFile()); if (!success) { - LoggerFactory.getLogger(NativeDesktop.class).warn("Could not move to trash. File {} is kept.", path); + LoggerFactory.getLogger(NativeDesktop.class) + .warn("Could not move to trash. File {} is kept.", path); } } diff --git a/src/main/java/org/jabref/gui/desktop/os/OSX.java b/src/main/java/org/jabref/gui/desktop/os/OSX.java index 33df47c886af..e034d9b2d1d6 100644 --- a/src/main/java/org/jabref/gui/desktop/os/OSX.java +++ b/src/main/java/org/jabref/gui/desktop/os/OSX.java @@ -1,9 +1,5 @@ package org.jabref.gui.desktop.os; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - import org.jabref.Launcher; import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.DialogService; @@ -11,6 +7,10 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.frame.ExternalApplicationsPreferences; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + /** * 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 Launcher#addLogToDisk} @@ -21,8 +21,14 @@ 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 { @@ -34,8 +40,10 @@ public void openFile(String filePath, String fileType, ExternalApplicationsPrefe @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}; + String[] cmd = + (application != null) && !application.isEmpty() + ? new String[] {"/usr/bin/open", "-a", application, filePath} + : new String[] {"/usr/bin/open", filePath}; new ProcessBuilder(cmd).start(); } @@ -47,7 +55,7 @@ public void openFolderAndSelectFile(Path file) throws IOException { @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 diff --git a/src/main/java/org/jabref/gui/desktop/os/Windows.java b/src/main/java/org/jabref/gui/desktop/os/Windows.java index 596c3d1ce54d..f74db3413c93 100644 --- a/src/main/java/org/jabref/gui/desktop/os/Windows.java +++ b/src/main/java/org/jabref/gui/desktop/os/Windows.java @@ -1,9 +1,9 @@ package org.jabref.gui.desktop.os; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; +import com.sun.jna.platform.win32.KnownFolders; +import com.sun.jna.platform.win32.Shell32Util; +import com.sun.jna.platform.win32.ShlObj; +import com.sun.jna.platform.win32.Win32Exception; import org.jabref.Launcher; import org.jabref.gui.DialogService; @@ -11,13 +11,13 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.logic.util.Directories; - -import com.sun.jna.platform.win32.KnownFolders; -import com.sun.jna.platform.win32.Shell32Util; -import com.sun.jna.platform.win32.ShlObj; -import com.sun.jna.platform.win32.Win32Exception; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + /** * 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 Launcher#addLogToDisk} @@ -27,8 +27,14 @@ 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()); diff --git a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java index ed57563c30c4..1a031a1d3052 100644 --- a/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java +++ b/src/main/java/org/jabref/gui/dialogs/AutosaveUiManager.java @@ -1,13 +1,13 @@ package org.jabref.gui.dialogs; +import com.google.common.eventbus.Subscribe; + import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.model.database.event.AutosaveEvent; import org.jabref.model.entry.BibEntryTypesManager; - -import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,8 +20,13 @@ public class AutosaveUiManager { private final SaveDatabaseAction saveDatabaseAction; - public AutosaveUiManager(LibraryTab libraryTab, DialogService dialogService, GuiPreferences preferences, BibEntryTypesManager entryTypesManager) { - this.saveDatabaseAction = new SaveDatabaseAction(libraryTab, dialogService, preferences, entryTypesManager); + public AutosaveUiManager( + LibraryTab libraryTab, + DialogService dialogService, + GuiPreferences preferences, + BibEntryTypesManager entryTypesManager) { + this.saveDatabaseAction = + new SaveDatabaseAction(libraryTab, dialogService, preferences, entryTypesManager); } @Subscribe diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 08dd120ab789..c14f20567911 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -1,12 +1,5 @@ package org.jabref.gui.dialogs; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import javafx.scene.control.ButtonType; import org.jabref.gui.DialogService; @@ -31,47 +24,67 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + /** * Stores all user dialogs related to {@link BackupManager}. */ public class BackupUIManager { private static final Logger LOGGER = LoggerFactory.getLogger(BackupUIManager.class); - private BackupUIManager() { - } + private BackupUIManager() {} - public static Optional showRestoreBackupDialog(DialogService dialogService, - Path originalPath, - GuiPreferences preferences, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - StateManager stateManager) { - var 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); - } - return Optional.empty(); - }); + public static Optional showRestoreBackupDialog( + DialogService dialogService, + Path originalPath, + GuiPreferences preferences, + FileUpdateMonitor fileUpdateMonitor, + UndoManager undoManager, + StateManager stateManager) { + var 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); + } + return Optional.empty(); + }); } - private static Optional showBackupResolverDialog(DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - Path originalPath, - Path backupDir) { + private static Optional showBackupResolverDialog( + DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences, + Path originalPath, + Path backupDir) { return UiTaskExecutor.runInJavaFXThread( - () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); + () -> + dialogService.showCustomDialogAndWait( + new BackupResolverDialog( + originalPath, backupDir, externalApplicationsPreferences))); } private static Optional showReviewBackupDialog( @@ -82,45 +95,75 @@ private static Optional showReviewBackupDialog( UndoManager undoManager, StateManager stateManager) { try { - ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences(); + 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" - ); - var allChangesResolved = dialogService.showCustomDialogAndWait(reviewBackupDialog); - LibraryTab 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)); - CE.end(); - 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 - 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) - saveState.markBaseChanged(); - } - // This does NOT return the original ParserResult, but a modified version with all changes accepted or rejected - return Optional.of(originalParserResult); - } + return UiTaskExecutor.runInJavaFXThread( + () -> { + List changes = + DatabaseChangeList.compareAndGetChanges( + originalDatabase, backupDatabase, changeResolverFactory); + DatabaseChangesResolverDialog reviewBackupDialog = + new DatabaseChangesResolverDialog( + changes, originalDatabase, "Review Backup"); + var allChangesResolved = + dialogService.showCustomDialogAndWait(reviewBackupDialog); + LibraryTab 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)); + CE.end(); + 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 + 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) + saveState.markBaseChanged(); + } + // 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); - }); + // In case not all changes are resolved, start from scratch + return showRestoreBackupDialog( + dialogService, + originalPath, + preferences, + fileUpdateMonitor, + undoManager, + stateManager); + }); } catch (IOException e) { LOGGER.error("Error while loading backup or current database", e); return Optional.empty(); diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java index 6871bcd6d5cc..7d9d1505f5f1 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerControl.java @@ -1,8 +1,6 @@ package org.jabref.gui.documentviewer; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; import javafx.animation.FadeTransition; import javafx.beans.property.DoubleProperty; @@ -19,13 +17,15 @@ import javafx.scene.shape.Rectangle; import javafx.util.Duration; -import org.jabref.logic.util.BackgroundTask; -import org.jabref.logic.util.TaskExecutor; - -import com.tobiasdiez.easybind.EasyBind; import org.fxmisc.flowless.Cell; import org.fxmisc.flowless.VirtualFlow; import org.fxmisc.flowless.VirtualFlowHit; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.TaskExecutor; + +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; public class DocumentViewerControl extends StackPane { @@ -71,22 +71,31 @@ private void showPage(int pageNumber) { public void show(DocumentViewModel document) { flow = VirtualFlow.createVertical(document.getPages(), DocumentViewerPage::new); getChildren().setAll(flow); - flow.visibleCells().addListener((ListChangeListener) c -> updateCurrentPage(flow.visibleCells())); + flow.visibleCells() + .addListener( + (ListChangeListener) + c -> updateCurrentPage(flow.visibleCells())); // (Bidirectional) binding does not work, so use listeners instead - flow.estimatedScrollYProperty().addListener((observable, oldValue, newValue) -> scrollY.setValue(newValue)); - scrollY.addListener((observable, oldValue, newValue) -> flow.estimatedScrollYProperty().setValue((double) newValue)); - flow.totalLengthEstimateProperty().addListener((observable, oldValue, newValue) -> scrollYMax.setValue(newValue)); - flow.addEventFilter(ScrollEvent.SCROLL, (ScrollEvent event) -> { - if (event.isControlDown()) { - event.consume(); - if (event.getDeltaY() > 0) { - changePageWidth(100); - } else { - changePageWidth(-100); - } - } - }); + flow.estimatedScrollYProperty() + .addListener((observable, oldValue, newValue) -> scrollY.setValue(newValue)); + scrollY.addListener( + (observable, oldValue, newValue) -> + flow.estimatedScrollYProperty().setValue((double) newValue)); + flow.totalLengthEstimateProperty() + .addListener((observable, oldValue, newValue) -> scrollYMax.setValue(newValue)); + flow.addEventFilter( + ScrollEvent.SCROLL, + (ScrollEvent event) -> { + if (event.isControlDown()) { + event.consume(); + if (event.getDeltaY() > 0) { + changePageWidth(100); + } else { + changePageWidth(-100); + } + } + }); } private void updateCurrentPage(ObservableList visiblePages) { @@ -112,7 +121,10 @@ private void updateCurrentPage(ObservableList visiblePages) } else { // Heuristic missed, so try to get page number from first shown page currentPage.set( - visiblePages.stream().findFirst().map(DocumentViewerPage::getPageNumber).orElse(1)); + visiblePages.stream() + .findFirst() + .map(DocumentViewerPage::getPageNumber) + .orElse(1)); } } @@ -143,7 +155,7 @@ public void changePageWidth(int delta) { // Limit zoom out to ~1 page due to occasional display errors when zooming out further int minWidth = (int) (flow.getHeight() / 2 * Math.sqrt(2)); if (newWidth < minWidth) { - if (newWidth - delta == minWidth) { // Attempting to zoom out when already at minWidth + if (newWidth - delta == minWidth) { // Attempting to zoom out when already at minWidth return; } newWidth = minWidth; @@ -176,13 +188,14 @@ public DocumentViewerPage(DocumentPageViewModel initialPage) { background = new Rectangle(getDesiredWidth(), getDesiredHeight()); background.setStyle("-fx-fill: WHITE"); // imageView.setImage(new WritableImage(getDesiredWidth(), getDesiredHeight())); - BackgroundTask generateImage = BackgroundTask - .wrap(() -> renderPage(initialPage)) - .onSuccess(image -> { - imageView.setImage(image); - progress.setVisible(false); - background.setVisible(false); - }); + BackgroundTask generateImage = + BackgroundTask.wrap(() -> renderPage(initialPage)) + .onSuccess( + image -> { + imageView.setImage(image); + progress.setVisible(false); + background.setVisible(false); + }); taskExecutor.execute(generateImage); imageHolder.getChildren().setAll(background, progress, imageView); @@ -210,23 +223,26 @@ public boolean isReusable() { public void updateItem(DocumentPageViewModel page) { this.page = page; - // First hide old page and show background instead (recalculate size of background to make sure its correct) + // First hide old page and show background instead (recalculate size of background to + // make sure its correct) background.setWidth(getDesiredWidth()); background.setHeight(getDesiredHeight()); background.setVisible(true); imageView.setOpacity(0); - BackgroundTask generateImage = BackgroundTask - .wrap(() -> renderPage(page)) - .onSuccess(image -> { - imageView.setImage(image); - - // Fade new page in for smoother transition - FadeTransition fadeIn = new FadeTransition(Duration.millis(100), imageView); - fadeIn.setFromValue(0); - fadeIn.setToValue(1); - fadeIn.play(); - }); + BackgroundTask generateImage = + BackgroundTask.wrap(() -> renderPage(page)) + .onSuccess( + image -> { + imageView.setImage(image); + + // Fade new page in for smoother transition + FadeTransition fadeIn = + new FadeTransition(Duration.millis(100), imageView); + fadeIn.setFromValue(0); + fadeIn.setToValue(1); + fadeIn.play(); + }); taskExecutor.execute(generateImage); } diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java index a2e774b53218..0da703e3781e 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerView.java @@ -1,5 +1,9 @@ package org.jabref.gui.documentviewer; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -24,9 +28,6 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.LinkedFile; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - public class DocumentViewerView extends BaseDialog { @FXML private ScrollBar scrollBar; @@ -51,11 +52,10 @@ 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); } @@ -73,23 +73,30 @@ private void initialize() { private void setupModeButtons() { // make sure that always one toggle is selected - toggleGroupMode.selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> { - if (newToggle == null) { - oldToggle.setSelected(true); - } - }); - - modeLive.selectedProperty().addListener((observable, oldValue, newValue) -> { - if (newValue) { - viewModel.setLiveMode(true); - } - }); - - modeLock.selectedProperty().addListener((observable, oldValue, newValue) -> { - if (newValue) { - viewModel.setLiveMode(false); - } - }); + toggleGroupMode + .selectedToggleProperty() + .addListener( + (observable, oldToggle, newToggle) -> { + if (newToggle == null) { + oldToggle.setSelected(true); + } + }); + + modeLive.selectedProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (newValue) { + viewModel.setLiveMode(true); + } + }); + + modeLock.selectedProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (newValue) { + viewModel.setLiveMode(false); + } + }); } private void setupScrollbar() { @@ -103,39 +110,50 @@ private void setupPageControls() { currentPage.setTextFormatter(integerFormatter); maxPages.textProperty().bind(viewModel.maxPagesProperty().asString()); previousButton.setDisable(true); - viewModel.currentPageProperty().addListener((observable, oldValue, newValue) -> { - nextButton.setDisable(newValue == viewModel.maxPagesProperty().get()); - previousButton.setDisable(newValue == 1); - }); + viewModel + .currentPageProperty() + .addListener( + (observable, oldValue, newValue) -> { + nextButton.setDisable(newValue == viewModel.maxPagesProperty().get()); + previousButton.setDisable(newValue == 1); + }); } private void setupFileChoice() { - ViewModelListCellFactory cellFactory = new ViewModelListCellFactory() - .withText(LinkedFile::getLink); + ViewModelListCellFactory cellFactory = + new ViewModelListCellFactory().withText(LinkedFile::getLink); fileChoice.setButtonCell(cellFactory.call(null)); fileChoice.setCellFactory(cellFactory); - fileChoice.getSelectionModel().selectedItemProperty().addListener( - (observable, oldValue, newValue) -> viewModel.switchToFile(newValue)); + fileChoice + .getSelectionModel() + .selectedItemProperty() + .addListener((observable, oldValue, newValue) -> viewModel.switchToFile(newValue)); // We always want that the first item is selected after a change // This also automatically selects the first file on the initial load Stage stage = (Stage) getDialogPane().getScene().getWindow(); - fileChoice.itemsProperty().addListener((observable, oldValue, newValue) -> { - if (newValue.isEmpty()) { - stage.close(); - } else { - fileChoice.getSelectionModel().selectFirst(); - } - }); + fileChoice + .itemsProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (newValue.isEmpty()) { + stage.close(); + } else { + fileChoice.getSelectionModel().selectFirst(); + } + }); fileChoice.itemsProperty().bind(viewModel.filesProperty()); } private void setupViewer() { viewer = new DocumentViewerControl(taskExecutor); - viewModel.currentDocumentProperty().addListener((observable, oldDocument, newDocument) -> { - if (newDocument != null) { - viewer.show(newDocument); - } - }); + viewModel + .currentDocumentProperty() + .addListener( + (observable, oldDocument, newDocument) -> { + if (newDocument != null) { + viewer.show(newDocument); + } + }); viewModel.currentPageProperty().bindBidirectional(viewer.currentPageProperty()); mainPane.setCenter(viewer); } diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java index 2be460966a18..c208ba35b748 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java @@ -1,11 +1,6 @@ package org.jabref.gui.documentviewer; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; @@ -18,6 +13,8 @@ import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; import org.jabref.gui.AbstractViewModel; import org.jabref.gui.StateManager; import org.jabref.gui.util.UiTaskExecutor; @@ -25,13 +22,16 @@ import org.jabref.logic.util.io.FileUtil; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; - -import com.tobiasdiez.easybind.EasyBind; -import org.apache.pdfbox.Loader; -import org.apache.pdfbox.pdmodel.PDDocument; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + public class DocumentViewerViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(DocumentViewerViewModel.class); @@ -48,23 +48,31 @@ public DocumentViewerViewModel(StateManager stateManager, CliPreferences prefere this.stateManager = Objects.requireNonNull(stateManager); this.preferences = Objects.requireNonNull(preferences); - this.stateManager.getSelectedEntries().addListener((ListChangeListener) c -> { - // Switch to currently selected entry in live mode - if (liveMode.get()) { - setCurrentEntries(this.stateManager.getSelectedEntries()); - } - }); - - this.liveMode.addListener((observable, oldValue, newValue) -> { - // Switch to currently selected entry if mode is changed to live - if ((oldValue != newValue) && newValue) { - setCurrentEntries(this.stateManager.getSelectedEntries()); - } - }); + this.stateManager + .getSelectedEntries() + .addListener( + (ListChangeListener) + c -> { + // Switch to currently selected entry in live mode + if (liveMode.get()) { + setCurrentEntries(this.stateManager.getSelectedEntries()); + } + }); + + this.liveMode.addListener( + (observable, oldValue, newValue) -> { + // Switch to currently selected entry if mode is changed to live + if ((oldValue != newValue) && newValue) { + setCurrentEntries(this.stateManager.getSelectedEntries()); + } + }); // we need to wrap this in run later so that the max pages number is correctly shown - UiTaskExecutor.runInJavaFXThread(() -> maxPages.bind( - EasyBind.wrapNullable(currentDocument).selectProperty(DocumentViewModel::maxPagesProperty))); + UiTaskExecutor.runInJavaFXThread( + () -> + maxPages.bind( + EasyBind.wrapNullable(currentDocument) + .selectProperty(DocumentViewModel::maxPagesProperty))); setCurrentEntries(this.stateManager.getSelectedEntries()); } @@ -92,7 +100,11 @@ private void setCurrentEntries(List entries) { if (entries.isEmpty()) { files.clear(); } else { - Set linkedFiles = entries.stream().map(BibEntry::getFiles).flatMap(List::stream).collect(Collectors.toSet()); + Set linkedFiles = + entries.stream() + .map(BibEntry::getFiles) + .flatMap(List::stream) + .collect(Collectors.toSet()); // We don't need to switch to the first file, this is done automatically in the UI part files.setValue(FXCollections.observableArrayList(linkedFiles)); } @@ -111,9 +123,10 @@ private void setCurrentDocument(Path path) { public void switchToFile(LinkedFile file) { if (file != null) { - stateManager.getActiveDatabase() - .flatMap(database -> file.findIn(database, preferences.getFilePreferences())) - .ifPresent(this::setCurrentDocument); + stateManager + .getActiveDatabase() + .flatMap(database -> file.findIn(database, preferences.getFilePreferences())) + .ifPresent(this::setCurrentDocument); currentPage.set(1); } } diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java index 607709742b1e..9b5126864a12 100644 --- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java @@ -1,21 +1,20 @@ package org.jabref.gui.documentviewer; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.Objects; - import javafx.scene.image.Image; import javafx.scene.image.PixelWriter; import javafx.scene.image.WritableImage; -import org.jabref.architecture.AllowedToUseAwt; - import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.PDFRenderer; +import org.jabref.architecture.AllowedToUseAwt; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Objects; /** * Represents the view model of a pdf page backed by a {@link PDPage}. @@ -35,7 +34,8 @@ public PdfDocumentPageViewModel(PDPage page, int pageNumber, PDDocument document // Taken from http://stackoverflow.com/a/9417836/873661 private static BufferedImage resize(BufferedImage img, int newWidth, int newHeight) { - java.awt.Image tmp = img.getScaledInstance(newWidth, newHeight, java.awt.Image.SCALE_SMOOTH); + java.awt.Image tmp = + img.getScaledInstance(newWidth, newHeight, java.awt.Image.SCALE_SMOOTH); BufferedImage dimg = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = dimg.createGraphics(); @@ -51,7 +51,8 @@ public Image render(int width, int height) { PDFRenderer renderer = new PDFRenderer(document); try { int resolution = 96; - BufferedImage image = renderer.renderImageWithDPI(pageNumber, 2 * resolution, ImageType.RGB); + BufferedImage image = + renderer.renderImageWithDPI(pageNumber, 2 * resolution, ImageType.RGB); return convertToFxImage(resize(image, width, height)); } catch (IOException e) { // TODO: LOG diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java index 7e5ae9a59691..5de6c8fd2a40 100644 --- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java @@ -1,15 +1,15 @@ package org.jabref.gui.documentviewer; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPageTree; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + public class PdfDocumentViewModel extends DocumentViewModel { private final PDDocument document; @@ -24,7 +24,8 @@ public ObservableList getPages() { PDPageTree pages = document.getDocumentCatalog().getPages(); List pdfPages = new ArrayList<>(); - // There is apparently no neat way to get the page number from a PDPage...thus this old-style for loop + // There is apparently no neat way to get the page number from a PDPage...thus this + // old-style for loop for (int i = 0; i < pages.getCount(); i++) { pdfPages.add(new PdfDocumentPageViewModel(pages.get(i), i, document)); } diff --git a/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java b/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java index c75bf81ee054..ffe4e34307e5 100644 --- a/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java +++ b/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java @@ -1,21 +1,26 @@ package org.jabref.gui.documentviewer; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; + +import com.airhacks.afterburner.injection.Injector; + import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.preferences.CliPreferences; -import com.airhacks.afterburner.injection.Injector; - -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; - public class ShowDocumentViewerAction extends SimpleCommand { - private final DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); + 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 diff --git a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java index 1dab94af6b92..3362085dd8c5 100644 --- a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java +++ b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateResolverDialog.java @@ -62,12 +62,13 @@ public static DuplicateResolverResult parse(String name) { 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 +84,10 @@ 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) { @@ -104,8 +108,13 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { first = new ButtonType(Localization.lang("Keep existing entry"), ButtonData.LEFT); second = new ButtonType(Localization.lang("Keep from import"), ButtonData.LEFT); both = new ButtonType(Localization.lang("Keep both"), ButtonData.LEFT); - threeWayMerge = new ThreeWayMergeView(one, two, Localization.lang("Existing entry"), - Localization.lang("From import"), preferences); + threeWayMerge = + new ThreeWayMergeView( + one, + two, + Localization.lang("Existing entry"), + Localization.lang("From import"), + preferences); } default -> throw new IllegalStateException("Switch expression should be exhaustive"); } @@ -117,13 +126,15 @@ 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 + // 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)); + .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()); @@ -133,28 +144,39 @@ private void init(BibEntry one, BibEntry two, DuplicateResolverType type) { BorderPane borderPane = new BorderPane(threeWayMerge); - 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())); - threeWayMerge.saveConfiguration(); - - if (button.equals(first)) { - return DuplicateResolverResult.KEEP_LEFT; - } else if (button.equals(second)) { - return DuplicateResolverResult.KEEP_RIGHT; - } else if (button.equals(both)) { - return DuplicateResolverResult.KEEP_BOTH; - } else if (button.equals(merge)) { - return DuplicateResolverResult.KEEP_MERGE; - } else if (button.equals(removeExact)) { - return DuplicateResolverResult.AUTOREMOVE_EXACT; - } else if (button.equals(cancel)) { - return DuplicateResolverResult.KEEP_LEFT; - } - return null; - }); - - HelpAction helpCommand = new HelpAction(HelpFile.FIND_DUPLICATES, dialogService, preferences.getExternalApplicationsPreferences()); + 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())); + threeWayMerge.saveConfiguration(); + + if (button.equals(first)) { + return DuplicateResolverResult.KEEP_LEFT; + } else if (button.equals(second)) { + return DuplicateResolverResult.KEEP_RIGHT; + } else if (button.equals(both)) { + return DuplicateResolverResult.KEEP_BOTH; + } else if (button.equals(merge)) { + return DuplicateResolverResult.KEEP_MERGE; + } else if (button.equals(removeExact)) { + return DuplicateResolverResult.AUTOREMOVE_EXACT; + } else if (button.equals(cancel)) { + return DuplicateResolverResult.KEEP_LEFT; + } + return null; + }); + + HelpAction helpCommand = + new HelpAction( + HelpFile.FIND_DUPLICATES, + dialogService, + preferences.getExternalApplicationsPreferences()); Button helpButton = actionFactory.createIconButton(StandardActions.HELP, helpCommand); borderPane.setRight(helpButton); diff --git a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java index f2e5ed1a0c8e..02c7d73368f3 100644 --- a/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java +++ b/src/main/java/org/jabref/gui/duplicationFinder/DuplicateSearch.java @@ -1,16 +1,6 @@ package org.jabref.gui.duplicationFinder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleIntegerProperty; @@ -37,7 +27,17 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; public class DuplicateSearch extends SimpleCommand { @@ -57,12 +57,13 @@ public class DuplicateSearch extends SimpleCommand { 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 +76,10 @@ 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 +92,16 @@ 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"); + HeadlessExecutorService.INSTANCE.executeInterruptableTask( + () -> searchPossibleDuplicates(entries, database.getMode()), "DuplicateSearcher"); BackgroundTask.wrap(this::verifyDuplicates) - .onSuccess(this::handleDuplicates) - .executeWith(taskExecutor); + .onSuccess(this::handleDuplicates) + .executeWith(taskExecutor); } private void searchPossibleDuplicates(List entries, BibDatabaseMode databaseMode) { @@ -106,7 +114,8 @@ private void searchPossibleDuplicates(List entries, BibDatabaseMode da BibEntry first = entries.get(i); BibEntry second = entries.get(j); - if (new DuplicateCheck(entryTypesManager).isDuplicate(first, second, databaseMode)) { + if (new DuplicateCheck(entryTypesManager) + .isDuplicate(first, second, databaseMode)) { duplicates.add(Arrays.asList(first, second)); duplicateCountObservable.set(String.valueOf(duplicateCount.incrementAndGet())); } @@ -123,7 +132,8 @@ 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; @@ -146,22 +156,39 @@ 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)); + UiTaskExecutor.runAndWaitInJavaFXThread( + () -> askResolveStrategy(result, first, second, resolverType)); } } return result; } - 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(")")); - - DuplicateResolverResult resolverResult = dialogService.showCustomDialogAndWait(dialog) - .orElse(DuplicateResolverResult.BREAK); + 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(")")); + + DuplicateResolverResult resolverResult = + dialogService.showCustomDialogAndWait(dialog).orElse(DuplicateResolverResult.BREAK); if ((resolverResult == DuplicateResolverResult.KEEP_LEFT) || (resolverResult == DuplicateResolverResult.AUTOREMOVE_EXACT)) { @@ -190,24 +217,33 @@ private void handleDuplicates(DuplicateSearchResult result) { } LibraryTab libraryTab = tabSupplier.get(); - final NamedCompound compoundEdit = new NamedCompound(Localization.lang("duplicate removal")); + final NamedCompound compoundEdit = + new NamedCompound(Localization.lang("duplicate removal")); // Now, do the actual removal: if (!result.getToRemove().isEmpty()) { - compoundEdit.addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), result.getToRemove())); + compoundEdit.addEdit( + new UndoableRemoveEntries(libraryTab.getDatabase(), result.getToRemove())); libraryTab.getDatabase().removeEntries(result.getToRemove()); libraryTab.markBaseChanged(); } // and adding merged entries: if (!result.getToAdd().isEmpty()) { - compoundEdit.addEdit(new UndoableInsertEntries(libraryTab.getDatabase(), result.getToAdd())); + compoundEdit.addEdit( + new UndoableInsertEntries(libraryTab.getDatabase(), result.getToAdd())); libraryTab.getDatabase().insertEntries(result.getToAdd()); libraryTab.markBaseChanged(); } duplicateProgress.set(0); - dialogService.notify(Localization.lang("Duplicates found") + ": " + duplicateCount.get() + ' ' - + Localization.lang("pairs processed") + ": " + result.getDuplicateCount()); + dialogService.notify( + Localization.lang("Duplicates found") + + ": " + + duplicateCount.get() + + ' ' + + Localization.lang("pairs processed") + + ": " + + result.getDuplicateCount()); compoundEdit.end(); libraryTab.getUndoManager().addEdit(compoundEdit); } diff --git a/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java b/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java index 7dec0abeae15..e7a8eac76713 100644 --- a/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyDoiUrlAction.java @@ -1,7 +1,5 @@ package org.jabref.gui.edit; -import java.util.Optional; - import javafx.scene.control.TextArea; import org.jabref.gui.ClipBoardManager; @@ -11,6 +9,8 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.identifier.DOI; +import java.util.Optional; + /** * Copies the doi url to the clipboard */ @@ -21,7 +21,11 @@ public class CopyDoiUrlAction extends SimpleCommand { private final DialogService dialogService; private final ClipBoardManager clipBoardManager; - public CopyDoiUrlAction(TextArea component, StandardActions action, DialogService dialogService, ClipBoardManager clipBoardManager) { + public CopyDoiUrlAction( + TextArea component, + StandardActions action, + DialogService dialogService, + ClipBoardManager clipBoardManager) { this.component = component; this.action = action; this.dialogService = dialogService; diff --git a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java index 0ef4ea2b74ab..ab263920c8ab 100644 --- a/src/main/java/org/jabref/gui/edit/CopyMoreAction.java +++ b/src/main/java/org/jabref/gui/edit/CopyMoreAction.java @@ -1,11 +1,5 @@ package org.jabref.gui.edit; -import java.io.IOException; -import java.io.StringReader; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefDialogService; @@ -23,10 +17,15 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + public class CopyMoreAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(CopyMoreAction.class); @@ -37,12 +36,13 @@ public class CopyMoreAction extends SimpleCommand { 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; @@ -55,35 +55,30 @@ public CopyMoreAction(StandardActions action, @Override public void execute() { - if (stateManager.getActiveDatabase().isEmpty() || stateManager.getSelectedEntries().isEmpty()) { + if (stateManager.getActiveDatabase().isEmpty() + || stateManager.getSelectedEntries().isEmpty()) { return; } 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."); } } private void copyTitle() { List selectedBibEntries = stateManager.getSelectedEntries(); - List titles = selectedBibEntries.stream() - .filter(bibEntry -> bibEntry.getTitle().isPresent()) - .map(bibEntry -> bibEntry.getTitle().get()) - .collect(Collectors.toList()); + List titles = + selectedBibEntries.stream() + .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.")); @@ -95,11 +90,16 @@ private void copyTitle() { if (titles.size() == selectedBibEntries.size()) { // All entries had titles. - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(copiedTitles))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedTitles))); } else { - dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined title.", - Integer.toString(selectedBibEntries.size() - titles.size()), Integer.toString(selectedBibEntries.size()))); + dialogService.notify( + Localization.lang( + "Warning: %0 out of %1 entries have undefined title.", + Integer.toString(selectedBibEntries.size() - titles.size()), + Integer.toString(selectedBibEntries.size()))); } } @@ -108,15 +108,19 @@ 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()); + copyDoiList( + entries.stream() + .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().getDOI()) - .collect(Collectors.toList()), entries.size()); + copyDoiList( + entries.stream() + .filter(entry -> entry.getDOI().isPresent()) + .map(entry -> entry.getDOI().get().getDOI()) + .collect(Collectors.toList()), + entries.size()); } } @@ -131,11 +135,15 @@ private void copyDoiList(List dois, int size) { if (dois.size() == size) { // All entries had DOIs. - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(copiedDois))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(copiedDois))); } else { - dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined DOIs.", - Integer.toString(size - dois.size()), Integer.toString(size))); + dialogService.notify( + Localization.lang( + "Warning: %0 out of %1 entries have undefined DOIs.", + Integer.toString(size - dois.size()), Integer.toString(size))); } } @@ -143,13 +151,15 @@ private void doCopyKey(Function, String> mapKeyList) { List entries = stateManager.getSelectedEntries(); // Collect all non-null keys. - List keys = entries.stream() - .filter(entry -> entry.getCitationKey().isPresent()) - .map(entry -> entry.getCitationKey().get()) - .collect(Collectors.toList()); + List keys = + entries.stream() + .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.")); + dialogService.notify( + Localization.lang("None of the selected entries have citation keys.")); return; } @@ -159,33 +169,56 @@ private void doCopyKey(Function, String> mapKeyList) { if (keys.size() == entries.size()) { // All entries had keys. - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(clipBoardContent))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(clipBoardContent))); } 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()))); + dialogService.notify( + Localization.lang( + "Warning: %0 out of %1 entries have undefined citation key.", + Integer.toString(entries.size() - keys.size()), + Integer.toString(entries.size()))); } } private void copyCiteKey() { - doCopyKey(keys -> { - CitationCommandString citeCommand = preferences.getExternalApplicationsPreferences().getCiteCommand(); - return citeCommand.prefix() + String.join(citeCommand.delimiter(), keys) + citeCommand.suffix(); - }); + doCopyKey( + keys -> { + CitationCommandString citeCommand = + preferences.getExternalApplicationsPreferences().getCiteCommand(); + return citeCommand.prefix() + + String.join(citeCommand.delimiter(), keys) + + citeCommand.suffix(); + }); } private void copyKey() { - doCopyKey(keys -> String.join(preferences.getExternalApplicationsPreferences().getCiteCommand().delimiter(), keys)); + doCopyKey( + keys -> + String.join( + preferences + .getExternalApplicationsPreferences() + .getCiteCommand() + .delimiter(), + keys)); } private void copyKeyAndTitle() { List entries = stateManager.getSelectedEntries(); // ToDo: this string should be configurable to allow arbitrary exports - StringReader layoutString = new StringReader("\\citationkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); + StringReader layoutString = + new StringReader( + "\\citationkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); Layout layout; try { - layout = new LayoutHelper(layoutString, preferences.getLayoutFormatterPreferences(), abbreviationRepository).getLayoutFromText(); + layout = + new LayoutHelper( + layoutString, + preferences.getLayoutFormatterPreferences(), + abbreviationRepository) + .getLayoutFromText(); } catch (IOException e) { LOGGER.info("Could not get layout.", e); return; @@ -198,14 +231,18 @@ private void copyKeyAndTitle() { for (BibEntry entry : entries) { if (entry.hasCitationKey()) { entriesWithKeys++; - stateManager.getActiveDatabase() - .map(BibDatabaseContext::getDatabase) - .ifPresent(bibDatabase -> keyAndTitle.append(layout.doLayout(entry, bibDatabase))); + stateManager + .getActiveDatabase() + .map(BibDatabaseContext::getDatabase) + .ifPresent( + bibDatabase -> + keyAndTitle.append(layout.doLayout(entry, bibDatabase))); } } if (entriesWithKeys == 0) { - dialogService.notify(Localization.lang("None of the selected entries have citation keys.")); + dialogService.notify( + Localization.lang("None of the selected entries have citation keys.")); return; } @@ -213,11 +250,16 @@ private void copyKeyAndTitle() { if (entriesWithKeys == entries.size()) { // All entries had keys. - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(keyAndTitle.toString()))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(keyAndTitle.toString()))); } else { - dialogService.notify(Localization.lang("Warning: %0 out of %1 entries have undefined citation key.", - Integer.toString(entries.size() - entriesWithKeys), Integer.toString(entries.size()))); + dialogService.notify( + Localization.lang( + "Warning: %0 out of %1 entries have undefined citation key.", + Integer.toString(entries.size() - entriesWithKeys), + Integer.toString(entries.size()))); } } @@ -232,12 +274,11 @@ 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.")); + dialogService.notify( + Localization.lang("None of the selected entries have citation keys.")); return; } @@ -257,11 +298,16 @@ private void copyKeyAndLink() { if (entriesWithKey.size() == entries.size()) { // All entries had keys. - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(keyAndLink.toString()))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(keyAndLink.toString()))); } 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()))); + 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/src/main/java/org/jabref/gui/edit/EditAction.java b/src/main/java/org/jabref/gui/edit/EditAction.java index c6be070e0e85..89a044997ac9 100644 --- a/src/main/java/org/jabref/gui/edit/EditAction.java +++ b/src/main/java/org/jabref/gui/edit/EditAction.java @@ -1,22 +1,21 @@ package org.jabref.gui.edit; -import java.util.function.Supplier; - -import javax.swing.undo.UndoManager; - import javafx.scene.control.TextInputControl; import javafx.scene.web.WebView; +import org.fxmisc.richtext.CodeArea; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; - -import org.fxmisc.richtext.CodeArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.function.Supplier; + +import javax.swing.undo.UndoManager; + /** * 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. @@ -30,7 +29,11 @@ public class EditAction extends SimpleCommand { 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; @@ -50,51 +53,63 @@ public String toString() { @Override public void execute() { - stateManager.getFocusOwner().ifPresent(focusOwner -> { - LOGGER.debug("focusOwner: {}; Action: {}", focusOwner, action.getText()); - if (focusOwner instanceof TextInputControl textInput) { - // Focus is on text field -> copy/paste/cut selected text - // DELETE_ENTRY in text field should do forward delete - switch (action) { - case SELECT_ALL -> textInput.selectAll(); - case COPY -> textInput.copy(); - case CUT -> textInput.cut(); - case PASTE -> textInput.paste(); - case DELETE -> textInput.clear(); - case DELETE_ENTRY -> textInput.deleteNextChar(); - case UNDO -> textInput.undo(); - case REDO -> textInput.redo(); - default -> { - String message = "Only cut/copy/paste supported in TextInputControl but got " + action; - LOGGER.error(message); - throw new IllegalStateException(message); - } - } - } else if ((focusOwner instanceof CodeArea) || (focusOwner instanceof WebView)) { - LOGGER.debug("Ignoring request in CodeArea or WebView"); - return; - } else { - LOGGER.debug("Else: {}", focusOwner.getClass().getSimpleName()); - // Not sure what is selected -> copy/paste/cut selected entries except for Preview and CodeArea + stateManager + .getFocusOwner() + .ifPresent( + focusOwner -> { + LOGGER.debug( + "focusOwner: {}; Action: {}", focusOwner, action.getText()); + if (focusOwner instanceof TextInputControl textInput) { + // Focus is on text field -> copy/paste/cut selected text + // DELETE_ENTRY in text field should do forward delete + switch (action) { + case SELECT_ALL -> textInput.selectAll(); + case COPY -> textInput.copy(); + case CUT -> textInput.cut(); + case PASTE -> textInput.paste(); + case DELETE -> textInput.clear(); + case DELETE_ENTRY -> textInput.deleteNextChar(); + case UNDO -> textInput.undo(); + case REDO -> textInput.redo(); + default -> { + String message = + "Only cut/copy/paste supported in TextInputControl but got " + + action; + LOGGER.error(message); + throw new IllegalStateException(message); + } + } + } else if ((focusOwner instanceof CodeArea) + || (focusOwner instanceof WebView)) { + LOGGER.debug("Ignoring request in CodeArea or WebView"); + return; + } else { + LOGGER.debug("Else: {}", focusOwner.getClass().getSimpleName()); + // Not sure what is selected -> copy/paste/cut selected entries + // except for Preview and CodeArea - switch (action) { - case COPY -> tabSupplier.get().copyEntry(); - case CUT -> tabSupplier.get().cutEntry(); - case PASTE -> tabSupplier.get().pasteEntry(); - case DELETE_ENTRY -> tabSupplier.get().deleteEntry(); - case UNDO -> { - if (undoManager.canUndo()) { - undoManager.undo(); - } - } - case REDO -> { - if (undoManager.canRedo()) { - undoManager.redo(); - } - } - default -> LOGGER.debug("Only cut/copy/paste/deleteEntry supported but got: {} and focus owner {}", action, focusOwner); - } - } - }); + switch (action) { + case COPY -> tabSupplier.get().copyEntry(); + case CUT -> tabSupplier.get().cutEntry(); + case PASTE -> tabSupplier.get().pasteEntry(); + case DELETE_ENTRY -> tabSupplier.get().deleteEntry(); + case UNDO -> { + if (undoManager.canUndo()) { + undoManager.undo(); + } + } + case REDO -> { + if (undoManager.canRedo()) { + undoManager.redo(); + } + } + default -> + LOGGER.debug( + "Only cut/copy/paste/deleteEntry supported but got: {} and focus owner {}", + action, + focusOwner); + } + } + }); } } diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java b/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java index 5ea7a6c95650..5fd356317fea 100644 --- a/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsAction.java @@ -1,16 +1,16 @@ package org.jabref.gui.edit; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; + +import com.airhacks.afterburner.injection.Injector; + import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.util.BindingsHelper; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.injection.Injector; - -import static org.jabref.gui.actions.ActionHelper.needsDatabase; -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; - /** * An Action for launching keyword managing dialog */ @@ -22,12 +22,17 @@ 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 public void execute() { DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); - dialogService.showCustomDialogAndWait(new ManageKeywordsDialog(stateManager.getSelectedEntries())); + dialogService.showCustomDialogAndWait( + new ManageKeywordsDialog(stateManager.getSelectedEntries())); } } diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java b/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java index 00d42f25e310..1aea1562109b 100644 --- a/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsDialog.java @@ -1,6 +1,9 @@ package org.jabref.gui.edit; -import java.util.List; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.ButtonType; @@ -17,9 +20,7 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.entry.BibEntry; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; +import java.util.List; public class ManageKeywordsDialog extends BaseDialog { private final List entries; @@ -35,48 +36,61 @@ 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) { - viewModel.saveChanges(); - } - return null; - }); + setResultConverter( + button -> { + if (button == ButtonType.APPLY) { + viewModel.saveChanges(); + } + return null; + }); } @FXML 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) - viewModel.getKeywords().set(event.getTablePosition().getRow(), event.getNewValue()); - }); + keywordsTableMainColumn.setCellValueFactory( + data -> BindingsHelper.constantOf(data.getValue())); + keywordsTableMainColumn.setOnEditCommit( + event -> { + // 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)) + .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)) + .withOnMouseClickedEvent( + (keyword, none) -> event -> viewModel.removeKeyword(keyword)) .install(keywordsTableDeleteColumn); } } diff --git a/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java b/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java index c4a5f8773a0c..b06bbe978b3a 100644 --- a/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java +++ b/src/main/java/org/jabref/gui/edit/ManageKeywordsViewModel.java @@ -1,7 +1,6 @@ package org.jabref.gui.edit; -import java.util.List; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -17,17 +16,20 @@ import org.jabref.model.entry.Keyword; import org.jabref.model.entry.KeywordList; -import com.tobiasdiez.easybind.EasyBind; +import java.util.List; +import java.util.Optional; 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) { + public ManageKeywordsViewModel( + BibEntryPreferences bibEntryPreferences, List entries) { this.bibEntryPreferences = bibEntryPreferences; this.entries = entries; this.keywords = FXCollections.observableArrayList(); @@ -60,7 +62,8 @@ private void fillKeywordsList(ManageKeywordsDisplayType type) { 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); @@ -108,8 +111,8 @@ public void saveChanges() { // TODO: bp.getUndoManager().addEdit(ce); } - private NamedCompound updateKeywords(List entries, KeywordList keywordsToAdd, - KeywordList keywordsToRemove) { + private NamedCompound updateKeywords( + List entries, KeywordList keywordsToAdd, KeywordList keywordsToRemove) { Character keywordSeparator = bibEntryPreferences.getKeywordSeparator(); NamedCompound ce = new NamedCompound(Localization.lang("Update keywords")); diff --git a/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java b/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java index 9711dd5478ef..a3baf46e7be6 100644 --- a/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java +++ b/src/main/java/org/jabref/gui/edit/OpenBrowserAction.java @@ -11,7 +11,10 @@ public class OpenBrowserAction extends SimpleCommand { 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; @@ -19,6 +22,7 @@ public OpenBrowserAction(String urlToOpen, DialogService dialogService, External @Override public void execute() { - NativeDesktop.openBrowserShowPopup(urlToOpen, dialogService, externalApplicationsPreferences); + NativeDesktop.openBrowserShowPopup( + urlToOpen, dialogService, externalApplicationsPreferences); } } diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java b/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java index 3a7ba00795cf..72aef28bdc0f 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringAction.java @@ -1,18 +1,21 @@ package org.jabref.gui.edit; -import java.util.function.Supplier; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; +import java.util.function.Supplier; + 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)); diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringView.java b/src/main/java/org/jabref/gui/edit/ReplaceStringView.java index a931406c99cd..4c428dfd534e 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringView.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringView.java @@ -1,5 +1,9 @@ package org.jabref.gui.edit; +import com.airhacks.afterburner.views.ViewLoader; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + import javafx.fxml.FXML; import javafx.scene.control.ButtonType; import javafx.scene.control.CheckBox; @@ -12,9 +16,6 @@ import org.jabref.gui.util.IconValidationDecorator; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - public class ReplaceStringView extends BaseDialog { @FXML private RadioButton allReplace; @@ -33,9 +34,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()); } diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java index 04db4bf91479..1945635c9dee 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java @@ -1,8 +1,5 @@ package org.jabref.gui.edit; -import java.util.Objects; -import java.util.Set; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -17,6 +14,9 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; +import java.util.Objects; +import java.util.Set; + public class ReplaceStringViewModel extends AbstractViewModel { private boolean allFieldReplace; private String findString; diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java index 2954b013f68a..5c9217dde7e3 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabView.java @@ -2,7 +2,8 @@ import javafx.scene.layout.AnchorPane; -public abstract class AbstractAutomaticFieldEditorTabView extends AnchorPane implements AutomaticFieldEditorTab { +public abstract class AbstractAutomaticFieldEditorTabView extends AnchorPane + implements AutomaticFieldEditorTab { @Override public AnchorPane getContent() { diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java index fe6e60fee01a..4227512117ef 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java @@ -1,12 +1,5 @@ package org.jabref.gui.edit.automaticfiededitor; -import java.util.Collection; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -15,18 +8,26 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + public abstract class AbstractAutomaticFieldEditorTabViewModel extends AbstractViewModel { - public static final Logger LOGGER = LoggerFactory.getLogger(AbstractAutomaticFieldEditorTabViewModel.class); + public static final Logger LOGGER = + LoggerFactory.getLogger(AbstractAutomaticFieldEditorTabViewModel.class); protected final StateManager stateManager; private final ObservableList allFields = FXCollections.observableArrayList(); - public AbstractAutomaticFieldEditorTabViewModel(BibDatabase bibDatabase, StateManager stateManager) { + public AbstractAutomaticFieldEditorTabViewModel( + BibDatabase bibDatabase, StateManager stateManager) { Objects.requireNonNull(bibDatabase); Objects.requireNonNull(stateManager); this.stateManager = stateManager; diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java index afd8adc29b9e..cd239ff0c4a1 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java @@ -1,20 +1,21 @@ package org.jabref.gui.edit.automaticfiededitor; -import javax.swing.undo.UndoManager; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; +import javax.swing.undo.UndoManager; 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) { + public AutomaticFieldEditorAction( + StateManager stateManager, DialogService dialogService, UndoManager undoManager) { this.stateManager = stateManager; this.dialogService = dialogService; this.undoManager = undoManager; @@ -24,6 +25,7 @@ public AutomaticFieldEditorAction(StateManager stateManager, DialogService dialo @Override public void execute() { - dialogService.showCustomDialogAndWait(new AutomaticFieldEditorDialog(stateManager, undoManager)); + dialogService.showCustomDialogAndWait( + new AutomaticFieldEditorDialog(stateManager, undoManager)); } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java index d2d0893a4887..b76c45c6496d 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java @@ -1,10 +1,7 @@ package org.jabref.gui.edit.automaticfiededitor; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.undo.CannotUndoException; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; import javafx.fxml.FXML; import javafx.scene.control.ButtonBar; @@ -16,18 +13,20 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; - -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.undo.CannotUndoException; +import javax.swing.undo.UndoManager; + public class AutomaticFieldEditorDialog extends BaseDialog { private static final Logger LOGGER = LoggerFactory.getLogger(AutomaticFieldEditorDialog.class); - @FXML - private TabPane tabPane; + @FXML private TabPane tabPane; private final UndoManager undoManager; @@ -48,41 +47,48 @@ 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 { - cancelChanges(); - } - return ""; - }); + setResultConverter( + buttonType -> { + if (buttonType != null + && buttonType.getButtonData() == ButtonBar.ButtonData.OK_DONE) { + saveChanges(); + } 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 + // 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)); + .map(getDialogPane()::lookupButton) + .forEach(btn -> ButtonBar.setButtonUniformSize(btn, false)); } @FXML public void initialize() { - viewModel = new AutomaticFieldEditorViewModel(selectedEntries, database, undoManager, stateManager); + viewModel = + new AutomaticFieldEditorViewModel( + selectedEntries, database, undoManager, stateManager); for (AutomaticFieldEditorTab tabModel : viewModel.getFieldEditorTabs()) { - NotificationPaneAdapter notificationPane = new NotificationPaneAdapter(tabModel.getContent()); + NotificationPaneAdapter notificationPane = + new NotificationPaneAdapter(tabModel.getContent()); notificationPanes.add(notificationPane); tabPane.getTabs().add(new Tab(tabModel.getTabName(), notificationPane)); } - EasyBind.listen(stateManager.lastAutomaticFieldEditorEditProperty(), (obs, old, lastEdit) -> { - viewModel.getDialogEdits().addEdit(lastEdit.getEdit()); - notificationPanes.get(lastEdit.getTabIndex()) - .notify(lastEdit.getAffectedEntries(), selectedEntries.size()); - }); + EasyBind.listen( + stateManager.lastAutomaticFieldEditorEditProperty(), + (obs, old, lastEdit) -> { + viewModel.getDialogEdits().addEdit(lastEdit.getEdit()); + notificationPanes + .get(lastEdit.getTabIndex()) + .notify(lastEdit.getAffectedEntries(), selectedEntries.size()); + }); } private void saveChanges() { diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java index 3f517a1dabd7..e72acafbd1a4 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.edit.automaticfiededitor; -import java.util.List; - -import javax.swing.undo.UndoManager; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -16,20 +12,28 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; +import java.util.List; + +import javax.swing.undo.UndoManager; + public class AutomaticFieldEditorViewModel extends AbstractViewModel { public static final String NAMED_COMPOUND_EDITS = "EDIT_FIELDS"; - private final ObservableList fieldEditorTabs = FXCollections.observableArrayList(); + 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), new CopyOrMoveFieldContentTabView(database, stateManager), - new RenameFieldTabView(database, stateManager) - ); + new RenameFieldTabView(database, stateManager)); } public NamedCompound getDialogEdits() { diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java index a1263576608d..35186d68a9cf 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java @@ -1,18 +1,19 @@ package org.jabref.gui.edit.automaticfiededitor; +import org.jabref.gui.undo.NamedCompound; + import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; -import org.jabref.gui.undo.NamedCompound; - public class LastAutomaticFieldEditorEdit extends AbstractUndoableEdit { private final Integer affectedEntries; private final NamedCompound edit; private final Integer tabIndex; - public LastAutomaticFieldEditorEdit(Integer affectedEntries, Integer tabIndex, NamedCompound edit) { + public LastAutomaticFieldEditorEdit( + Integer affectedEntries, Integer tabIndex, NamedCompound edit) { this.affectedEntries = affectedEntries; this.edit = edit; this.tabIndex = tabIndex; diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java index 660c2f8454b4..7d23bdedf4c9 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java @@ -1,7 +1,5 @@ package org.jabref.gui.edit.automaticfiededitor; -import java.util.List; - import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; @@ -9,6 +7,8 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; +import java.util.List; + public class MoveFieldValueAction extends SimpleCommand { private final Field fromField; private final Field toField; @@ -20,7 +20,12 @@ 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; @@ -28,7 +33,8 @@ public MoveFieldValueAction(Field fromField, Field toField, List entri this.overwriteToFieldContent = overwriteToFieldContent; } - public MoveFieldValueAction(Field fromField, Field toField, List entries, NamedCompound edits) { + public MoveFieldValueAction( + Field fromField, Field toField, List entries, NamedCompound edits) { this(fromField, toField, entries, edits, true); } @@ -44,7 +50,8 @@ public void execute() { entry.setField(fromField, ""); edits.addEdit(new UndoableFieldChange(entry, fromField, fromFieldValue, null)); - edits.addEdit(new UndoableFieldChange(entry, toField, toFieldValue, fromFieldValue)); + edits.addEdit( + new UndoableFieldChange(entry, toField, toFieldValue, fromFieldValue)); affectedEntriesCount++; } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java index 811a954a8aae..49eb3b7778fa 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java @@ -1,13 +1,13 @@ package org.jabref.gui.edit.automaticfiededitor; -import java.util.Collections; - import javafx.scene.Node; import javafx.util.Duration; import org.jabref.gui.LibraryTab; import org.jabref.gui.icon.IconTheme; +import java.util.Collections; + public class NotificationPaneAdapter extends LibraryTab.DatabaseNotification { public NotificationPaneAdapter(Node content) { @@ -15,9 +15,14 @@ public NotificationPaneAdapter(Node content) { } public void notify(int affectedEntries, int totalEntries) { - String notificationMessage = "%d/%d affected entries".formatted(affectedEntries, totalEntries); + String notificationMessage = + "%d/%d affected entries".formatted(affectedEntries, totalEntries); Node notificationGraphic = IconTheme.JabRefIcons.INTEGRITY_INFO.getGraphicNode(); - notify(notificationGraphic, notificationMessage, Collections.emptyList(), Duration.seconds(2)); + notify( + notificationGraphic, + notificationMessage, + Collections.emptyList(), + Duration.seconds(2)); } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java index 179004ec0ac7..900275ffd511 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java @@ -1,7 +1,11 @@ package org.jabref.gui.edit.automaticfiededitor.copyormovecontent; -import java.util.ArrayList; -import java.util.List; +import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; + +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; import javafx.application.Platform; import javafx.fxml.FXML; @@ -17,27 +21,20 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - -import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; +import java.util.ArrayList; +import java.util.List; -public class CopyOrMoveFieldContentTabView extends AbstractAutomaticFieldEditorTabView implements AutomaticFieldEditorTab { +public class CopyOrMoveFieldContentTabView extends AbstractAutomaticFieldEditorTabView + implements AutomaticFieldEditorTab { public Button copyContentButton; - @FXML - private Button moveContentButton; + @FXML private Button moveContentButton; - @FXML - private Button swapContentButton; + @FXML private Button swapContentButton; - @FXML - private ComboBox fromFieldComboBox; - @FXML - private ComboBox toFieldComboBox; + @FXML private ComboBox fromFieldComboBox; + @FXML private ComboBox toFieldComboBox; - @FXML - private CheckBox overwriteFieldContentCheckBox; + @FXML private CheckBox overwriteFieldContentCheckBox; private CopyOrMoveFieldContentTabViewModel viewModel; private final List selectedEntries; @@ -51,25 +48,31 @@ 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() { viewModel = new CopyOrMoveFieldContentTabViewModel(selectedEntries, database, stateManager); initializeFromAndToComboBox(); - viewModel.overwriteFieldContentProperty().bindBidirectional(overwriteFieldContentCheckBox.selectedProperty()); + viewModel + .overwriteFieldContentProperty() + .bindBidirectional(overwriteFieldContentCheckBox.selectedProperty()); moveContentButton.disableProperty().bind(viewModel.canMoveProperty().not()); swapContentButton.disableProperty().bind(viewModel.canSwapProperty().not()); - copyContentButton.disableProperty().bind(viewModel.toFieldValidationStatus().validProperty().not()); - overwriteFieldContentCheckBox.disableProperty().bind(viewModel.toFieldValidationStatus().validProperty().not()); - - Platform.runLater(() -> { - visualizer.initVisualization(viewModel.toFieldValidationStatus(), toFieldComboBox, true); - }); + copyContentButton + .disableProperty() + .bind(viewModel.toFieldValidationStatus().validProperty().not()); + overwriteFieldContentCheckBox + .disableProperty() + .bind(viewModel.toFieldValidationStatus().validProperty().not()); + + Platform.runLater( + () -> { + visualizer.initVisualization( + viewModel.toFieldValidationStatus(), toFieldComboBox, true); + }); } private void initializeFromAndToComboBox() { @@ -83,8 +86,12 @@ private void initializeFromAndToComboBox() { fromFieldComboBox.valueProperty().bindBidirectional(viewModel.fromFieldProperty()); toFieldComboBox.valueProperty().bindBidirectional(viewModel.toFieldProperty()); - EasyBind.listen(fromFieldComboBox.getEditor().textProperty(), observable -> fromFieldComboBox.commitValue()); - EasyBind.listen(toFieldComboBox.getEditor().textProperty(), observable -> toFieldComboBox.commitValue()); + EasyBind.listen( + fromFieldComboBox.getEditor().textProperty(), + observable -> fromFieldComboBox.commitValue()); + EasyBind.listen( + toFieldComboBox.getEditor().textProperty(), + observable -> toFieldComboBox.commitValue()); } @Override diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java index 3ed3a1b38eea..4af7d60a40ef 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java @@ -1,7 +1,9 @@ package org.jabref.gui.edit.automaticfiededitor.copyormovecontent; -import java.util.ArrayList; -import java.util.List; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty; @@ -21,14 +23,13 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.ArrayList; +import java.util.List; public class CopyOrMoveFieldContentTabViewModel extends AbstractAutomaticFieldEditorTabViewModel { public static final int TAB_INDEX = 1; - private final ObjectProperty fromField = new SimpleObjectProperty<>(StandardField.ABSTRACT); + private final ObjectProperty fromField = + new SimpleObjectProperty<>(StandardField.ABSTRACT); private final ObjectProperty toField = new SimpleObjectProperty<>(StandardField.AUTHOR); @@ -41,24 +42,31 @@ 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())) { - return ValidationMessage.error("Field name cannot have whitespace characters"); - } - return null; - }); - - canMove = BooleanBinding.booleanExpression(toFieldValidationStatus().validProperty()) - .and(overwriteFieldContentProperty()); - - canSwap = BooleanBinding.booleanExpression(toFieldValidationStatus().validProperty()) - .and(overwriteFieldContentProperty()); + toFieldValidator = + new FunctionBasedValidator<>( + toField, + field -> { + if (StringUtil.isBlank(field.getName())) { + return ValidationMessage.error("Field name cannot be empty"); + } else if (StringUtil.containsWhitespace(field.getName())) { + return ValidationMessage.error( + "Field name cannot have whitespace characters"); + } + return null; + }); + + canMove = + BooleanBinding.booleanExpression(toFieldValidationStatus().validProperty()) + .and(overwriteFieldContentProperty()); + + canSwap = + BooleanBinding.booleanExpression(toFieldValidationStatus().validProperty()) + .and(overwriteFieldContentProperty()); } public ValidationStatus toFieldValidationStatus() { @@ -107,10 +115,9 @@ 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 +125,26 @@ 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 +154,19 @@ 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,9 +174,9 @@ 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() { diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java index 0c97b3f81e6c..e4fc20fe7a56 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java @@ -1,6 +1,11 @@ package org.jabref.gui.edit.automaticfiededitor.editfieldcontent; -import java.util.List; +import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; + +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; import javafx.application.Platform; import javafx.fxml.FXML; @@ -16,24 +21,17 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - -import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; +import java.util.List; public class EditFieldContentTabView extends AbstractAutomaticFieldEditorTabView { public Button appendValueButton; public Button clearFieldButton; public Button setValueButton; - @FXML - private ComboBox fieldComboBox; + @FXML private ComboBox fieldComboBox; - @FXML - private TextField fieldValueTextField; + @FXML private TextField fieldValueTextField; - @FXML - private CheckBox overwriteFieldContentCheckBox; + @FXML private CheckBox overwriteFieldContentCheckBox; private final List selectedEntries; private final BibDatabase database; @@ -49,9 +47,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 @@ -64,18 +60,31 @@ public void initialize() { fieldComboBox.getSelectionModel().selectFirst(); fieldComboBox.valueProperty().bindBidirectional(viewModel.selectedFieldProperty()); - EasyBind.listen(fieldComboBox.getEditor().textProperty(), observable -> fieldComboBox.commitValue()); + EasyBind.listen( + fieldComboBox.getEditor().textProperty(), + observable -> fieldComboBox.commitValue()); fieldValueTextField.textProperty().bindBidirectional(viewModel.fieldValueProperty()); - overwriteFieldContentCheckBox.selectedProperty().bindBidirectional(viewModel.overwriteFieldContentProperty()); + overwriteFieldContentCheckBox + .selectedProperty() + .bindBidirectional(viewModel.overwriteFieldContentProperty()); appendValueButton.disableProperty().bind(viewModel.canAppendProperty().not()); - setValueButton.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not()); - clearFieldButton.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not()); - overwriteFieldContentCheckBox.disableProperty().bind(viewModel.fieldValidationStatus().validProperty().not()); - - Platform.runLater(() -> visualizer.initVisualization(viewModel.fieldValidationStatus(), fieldComboBox, true)); + setValueButton + .disableProperty() + .bind(viewModel.fieldValidationStatus().validProperty().not()); + clearFieldButton + .disableProperty() + .bind(viewModel.fieldValidationStatus().validProperty().not()); + overwriteFieldContentCheckBox + .disableProperty() + .bind(viewModel.fieldValidationStatus().validProperty().not()); + + Platform.runLater( + () -> + visualizer.initVisualization( + viewModel.fieldValidationStatus(), fieldComboBox, true)); } @Override diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java index 933e1d4f87bf..0aa69b3982cb 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java @@ -1,8 +1,9 @@ package org.jabref.gui.edit.automaticfiededitor.editfieldcontent; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -24,10 +25,9 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; public class EditFieldContentViewModel extends AbstractAutomaticFieldEditorTabViewModel { public static final int TAB_INDEX = 0; @@ -36,27 +36,35 @@ public class EditFieldContentViewModel extends AbstractAutomaticFieldEditorTabVi private final StringProperty fieldValue = new SimpleStringProperty(""); - private final ObjectProperty selectedField = new SimpleObjectProperty<>(StandardField.AUTHOR); + private final ObjectProperty selectedField = + new SimpleObjectProperty<>(StandardField.AUTHOR); private final BooleanProperty overwriteFieldContent = new SimpleBooleanProperty(Boolean.FALSE); private final Validator fieldValidator; private final BooleanBinding canAppend; - public EditFieldContentViewModel(BibDatabase database, List selectedEntries, StateManager stateManager) { + public EditFieldContentViewModel( + BibDatabase database, List selectedEntries, StateManager stateManager) { super(database, stateManager); this.selectedEntries = new ArrayList<>(selectedEntries); - fieldValidator = new FunctionBasedValidator<>(selectedField, field -> { - if (StringUtil.isBlank(field.getName())) { - return ValidationMessage.error("Field name cannot be empty"); - } else if (StringUtil.containsWhitespace(field.getName())) { - return ValidationMessage.error("Field name cannot have whitespace characters"); - } - return null; - }); - - canAppend = Bindings.and(overwriteFieldContentProperty(), fieldValidationStatus().validProperty()); + fieldValidator = + new FunctionBasedValidator<>( + selectedField, + field -> { + if (StringUtil.isBlank(field.getName())) { + return ValidationMessage.error("Field name cannot be empty"); + } else if (StringUtil.containsWhitespace(field.getName())) { + return ValidationMessage.error( + "Field name cannot have whitespace characters"); + } + return null; + }); + + canAppend = + Bindings.and( + overwriteFieldContentProperty(), fieldValidationStatus().validProperty()); } public ValidationStatus fieldValidationStatus() { @@ -74,7 +82,10 @@ 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 +93,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,7 +105,9 @@ 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() affectedEntriesCount++; @@ -107,11 +117,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 +132,10 @@ 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 +145,9 @@ 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() { diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java index fc111cecc463..2fe40e7ff78d 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldTabView.java @@ -1,6 +1,11 @@ package org.jabref.gui.edit.automaticfiededitor.renamefield; -import java.util.List; +import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; + +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; import javafx.application.Platform; import javafx.fxml.FXML; @@ -16,19 +21,13 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - -import static org.jabref.gui.util.FieldsUtil.FIELD_STRING_CONVERTER; +import java.util.List; -public class RenameFieldTabView extends AbstractAutomaticFieldEditorTabView implements AutomaticFieldEditorTab { - @FXML - private Button renameButton; - @FXML - private ComboBox fieldComboBox; - @FXML - private TextField newFieldNameTextField; +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; @@ -41,9 +40,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 @@ -56,15 +53,19 @@ public void initialize() { fieldComboBox.setConverter(FIELD_STRING_CONVERTER); fieldComboBox.valueProperty().bindBidirectional(viewModel.selectedFieldProperty()); - EasyBind.listen(fieldComboBox.getEditor().textProperty(), observable -> fieldComboBox.commitValue()); + EasyBind.listen( + fieldComboBox.getEditor().textProperty(), + observable -> fieldComboBox.commitValue()); renameButton.disableProperty().bind(viewModel.canRenameProperty().not()); newFieldNameTextField.textProperty().bindBidirectional(viewModel.newFieldNameProperty()); - Platform.runLater(() -> { - visualizer.initVisualization(viewModel.fieldNameValidationStatus(), newFieldNameTextField, true); - }); + Platform.runLater( + () -> { + visualizer.initVisualization( + viewModel.fieldNameValidationStatus(), newFieldNameTextField, true); + }); } @Override diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java index 6f22623d4669..9e7f942413f8 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/renamefield/RenameFieldViewModel.java @@ -1,6 +1,9 @@ package org.jabref.gui.edit.automaticfiededitor.renamefield; -import java.util.List; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -21,15 +24,13 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.List; 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 ObjectProperty selectedField = + new SimpleObjectProperty<>(StandardField.AUTHOR); private final List selectedEntries; private final Validator fieldValidator; @@ -38,22 +39,33 @@ public class RenameFieldViewModel extends AbstractAutomaticFieldEditorTabViewMod private final BooleanBinding canRename; - public RenameFieldViewModel(List selectedEntries, BibDatabase database, StateManager stateManager) { + public RenameFieldViewModel( + List selectedEntries, BibDatabase database, StateManager stateManager) { super(database, stateManager); this.selectedEntries = selectedEntries; - fieldValidator = new FunctionBasedValidator<>(selectedField, field -> StringUtil.isNotBlank(field.getName()), - ValidationMessage.error("Field cannot be empty")); - fieldNameValidator = new FunctionBasedValidator<>(newFieldName, fieldName -> { - if (StringUtil.isBlank(fieldName)) { - return ValidationMessage.error("Field name cannot be empty"); - } else if (StringUtil.containsWhitespace(fieldName)) { - return ValidationMessage.error("Field name cannot have whitespace characters"); - } - return null; - }); - - canRename = Bindings.and(fieldValidationStatus().validProperty(), fieldNameValidationStatus().validProperty()); + fieldValidator = + new FunctionBasedValidator<>( + selectedField, + field -> StringUtil.isNotBlank(field.getName()), + ValidationMessage.error("Field cannot be empty")); + fieldNameValidator = + new FunctionBasedValidator<>( + newFieldName, + fieldName -> { + if (StringUtil.isBlank(fieldName)) { + return ValidationMessage.error("Field name cannot be empty"); + } else if (StringUtil.containsWhitespace(fieldName)) { + return ValidationMessage.error( + "Field name cannot have whitespace characters"); + } + return null; + }); + + canRename = + Bindings.and( + fieldValidationStatus().validProperty(), + fieldNameValidationStatus().validProperty()); } public ValidationStatus fieldValidationStatus() { @@ -96,19 +108,21 @@ public void renameField() { NamedCompound renameEdit = new NamedCompound("RENAME_EDIT"); int affectedEntriesCount = 0; if (fieldNameValidationStatus().isValid()) { - affectedEntriesCount = new MoveFieldValueAction(selectedField.get(), - FieldFactory.parseField(newFieldName.get()), - selectedEntries, - renameEdit, - false).executeAndGetAffectedEntriesCount(); + affectedEntriesCount = + new MoveFieldValueAction( + selectedField.get(), + 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/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java b/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java index 95e2b7bf102a..dfb79318768a 100644 --- a/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/AiChatTab.java @@ -1,10 +1,5 @@ package org.jabref.gui.entryeditor; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; @@ -28,6 +23,11 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + public class AiChatTab extends EntryEditorTab { private final BibDatabaseContext bibDatabaseContext; private final AiService aiService; @@ -41,13 +41,13 @@ public class AiChatTab extends EntryEditorTab { private Optional previousBibEntry = Optional.empty(); - public AiChatTab(BibDatabaseContext bibDatabaseContext, - AiService aiService, - ChatHistoryService chatHistoryService, - DialogService dialogService, - GuiPreferences preferences, - TaskExecutor taskExecutor - ) { + public AiChatTab( + BibDatabaseContext bibDatabaseContext, + AiService aiService, + ChatHistoryService chatHistoryService, + DialogService dialogService, + GuiPreferences preferences, + TaskExecutor taskExecutor) { this.bibDatabaseContext = bibDatabaseContext; this.aiService = aiService; @@ -58,12 +58,15 @@ public AiChatTab(BibDatabaseContext bibDatabaseContext, this.externalApplicationsPreferences = preferences.getExternalApplicationsPreferences(); this.entryEditorPreferences = preferences.getEntryEditorPreferences(); - this.citationKeyGenerator = new CitationKeyGenerator(bibDatabaseContext, preferences.getCitationKeyPatternPreferences()); + this.citationKeyGenerator = + new CitationKeyGenerator( + bibDatabaseContext, preferences.getCitationKeyPatternPreferences()); this.taskExecutor = taskExecutor; setText(Localization.lang("AI chat")); - setTooltip(new Tooltip(Localization.lang("Chat with AI about content of attached file(s)"))); + setTooltip( + new Tooltip(Localization.lang("Chat with AI about content of attached file(s)"))); } @Override @@ -76,14 +79,18 @@ public boolean shouldShow(BibEntry entry) { */ @Override protected void bindToEntry(BibEntry entry) { - previousBibEntry.ifPresent(previousBibEntry -> chatHistoryService.closeChatHistoryForEntry(previousBibEntry)); + previousBibEntry.ifPresent( + previousBibEntry -> chatHistoryService.closeChatHistoryForEntry(previousBibEntry)); previousBibEntry = Optional.of(entry); if (!aiPreferences.getEnableAi()) { showPrivacyNotice(entry); } 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)) { tryToGenerateCitationKeyThenBind(entry); @@ -93,25 +100,27 @@ protected void bindToEntry(BibEntry entry) { } private void showPrivacyNotice(BibEntry entry) { - setContent(new PrivacyNoticeComponent(aiPreferences, () -> bindToEntry(entry), externalApplicationsPreferences, dialogService)); + setContent( + new PrivacyNoticeComponent( + aiPreferences, + () -> bindToEntry(entry), + externalApplicationsPreferences, + dialogService)); } private void showErrorNotPdfs() { setContent( new ErrorStateComponent( Localization.lang("Unable to chat"), - Localization.lang("Only PDF files are supported.") - ) - ); + 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).") - ) - ); + Localization.lang( + "Please attach at least one PDF file to enable chatting with PDF file(s)."))); } private void tryToGenerateCitationKeyThenBind(BibEntry entry) { @@ -119,30 +128,34 @@ private void tryToGenerateCitationKeyThenBind(BibEntry entry) { setContent( new ErrorStateComponent( Localization.lang("Unable to chat"), - Localization.lang("Please provide a non-empty and unique citation key for this entry.") - ) - ); + Localization.lang( + "Please provide a non-empty and unique citation key for this entry."))); } else { bindToEntry(entry); } } private void bindToCorrectEntry(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, - chatHistoryService.getChatHistoryForEntry(entry), - bibDatabaseContext, - FXCollections.observableArrayList(new ArrayList<>(List.of(entry))), - aiService, - dialogService, - aiPreferences, - externalApplicationsPreferences, - taskExecutor - )); + StringProperty chatName = + new SimpleStringProperty( + "entry " + entry.getCitationKey().orElse("")); + entry.getCiteKeyBinding() + .addListener( + (observable, oldValue, newValue) -> chatName.setValue("entry " + newValue)); + + setContent( + new AiChatGuardedComponent( + chatName, + chatHistoryService.getChatHistoryForEntry(entry), + bibDatabaseContext, + FXCollections.observableArrayList(new ArrayList<>(List.of(entry))), + aiService, + dialogService, + aiPreferences, + externalApplicationsPreferences, + taskExecutor)); } } diff --git a/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java b/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java index 6e77c34270fc..7272b76db3fd 100644 --- a/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/AiSummaryTab.java @@ -22,11 +22,11 @@ public class AiSummaryTab extends EntryEditorTab { private final CitationKeyPatternPreferences citationKeyPatternPreferences; private final EntryEditorPreferences entryEditorPreferences; - public AiSummaryTab(BibDatabaseContext bibDatabaseContext, - AiService aiService, - DialogService dialogService, - GuiPreferences preferences - ) { + public AiSummaryTab( + BibDatabaseContext bibDatabaseContext, + AiService aiService, + DialogService dialogService, + GuiPreferences preferences) { this.bibDatabaseContext = bibDatabaseContext; this.aiService = aiService; @@ -51,14 +51,14 @@ public boolean shouldShow(BibEntry entry) { */ @Override protected void bindToEntry(BibEntry entry) { - setContent(new SummaryComponent( - bibDatabaseContext, - entry, - aiService, - aiPreferences, - externalApplicationsPreferences, - citationKeyPatternPreferences, - dialogService - )); + setContent( + new SummaryComponent( + bibDatabaseContext, + entry, + aiService, + aiPreferences, + externalApplicationsPreferences, + citationKeyPatternPreferences, + dialogService)); } } diff --git a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java index 428052847bf5..f37b22f99e56 100644 --- a/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/CommentsTab.java @@ -1,15 +1,5 @@ package org.jabref.gui.entryeditor; -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.SequencedSet; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; - import javafx.collections.ObservableList; import javafx.geometry.VPos; import javafx.scene.control.Button; @@ -37,6 +27,16 @@ import org.jabref.model.entry.field.UserSpecificCommentField; import org.jabref.model.search.SearchQuery; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.SequencedSet; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + public class CommentsTab extends FieldsEditorTab { public static final String NAME = "Comments"; @@ -45,18 +45,19 @@ public class CommentsTab extends FieldsEditorTab { private final EntryEditorPreferences entryEditorPreferences; - public CommentsTab(GuiPreferences preferences, - BibDatabaseContext databaseContext, - SuggestionProviders suggestionProviders, - UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - DialogService dialogService, - ThemeManager themeManager, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, - OptionalObjectProperty searchQueryProperty) { + public CommentsTab( + GuiPreferences preferences, + BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + UndoAction undoAction, + RedoAction redoAction, + DialogService dialogService, + ThemeManager themeManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository, + LuceneManager luceneManager, + OptionalObjectProperty searchQueryProperty) { super( false, databaseContext, @@ -70,9 +71,13 @@ public CommentsTab(GuiPreferences preferences, taskExecutor, journalAbbreviationRepository, luceneManager, - searchQueryProperty - ); - this.defaultOwner = preferences.getOwnerPreferences().getDefaultOwner().toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", "-"); + searchQueryProperty); + this.defaultOwner = + preferences + .getOwnerPreferences() + .getDefaultOwner() + .toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9]", "-"); setText(Localization.lang("Comments")); setGraphic(IconTheme.JabRefIcons.COMMENT.getGraphicNode()); @@ -88,16 +93,23 @@ protected SequencedSet determineFieldsToShow(BibEntry entry) { comments.add(StandardField.COMMENT); // Also show comment field of the current user (if enabled in the preferences) - if (entry.hasField(userSpecificCommentField) || entryEditorPreferences.shouldShowUserCommentsFields()) { + if (entry.hasField(userSpecificCommentField) + || entryEditorPreferences.shouldShowUserCommentsFields()) { comments.add(userSpecificCommentField); } // 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; } @@ -132,7 +144,11 @@ private void setCompressedRowLayout() { protected void setupPanel(BibEntry entry, boolean compressed) { super.setupPanel(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(); FieldEditorFX editor = fieldEditorEntry.getValue(); @@ -143,19 +159,34 @@ protected void setupPanel(BibEntry entry, boolean compressed) { editor.getNode().setDisable(!shouldBeEnabled); } - // Show "Hide" button only if user-specific comment field is empty. Otherwise, it is a strange UI, because the + // Show "Hide" button only if user-specific comment field is empty. Otherwise, it is a + // strange UI, because the // button would just disappear and no change **in the current** editor would be made - if (entryEditorPreferences.shouldShowUserCommentsFields() && !entry.hasField(userSpecificCommentField)) { - Button hideDefaultOwnerCommentButton = new Button(Localization.lang("Hide user comments")); - hideDefaultOwnerCommentButton.setOnAction(e -> { - var labelForField = gridPane.getChildren().stream().filter(s -> s instanceof FieldNameLabel).filter(x -> ((FieldNameLabel) x).getText().equals(userSpecificCommentField.getDisplayName())).findFirst(); - labelForField.ifPresent(label -> gridPane.getChildren().remove(label)); - fieldEditorForUserDefinedComment.ifPresent(f -> gridPane.getChildren().remove(f.getNode())); - editors.remove(userSpecificCommentField); - - entryEditorPreferences.setShowUserCommentsFields(false); - setupPanel(entry, false); - }); + if (entryEditorPreferences.shouldShowUserCommentsFields() + && !entry.hasField(userSpecificCommentField)) { + Button hideDefaultOwnerCommentButton = + new Button(Localization.lang("Hide user comments")); + hideDefaultOwnerCommentButton.setOnAction( + e -> { + var labelForField = + gridPane.getChildren().stream() + .filter(s -> s instanceof FieldNameLabel) + .filter( + x -> + ((FieldNameLabel) x) + .getText() + .equals( + userSpecificCommentField + .getDisplayName())) + .findFirst(); + labelForField.ifPresent(label -> gridPane.getChildren().remove(label)); + fieldEditorForUserDefinedComment.ifPresent( + f -> gridPane.getChildren().remove(f.getNode())); + editors.remove(userSpecificCommentField); + + entryEditorPreferences.setShowUserCommentsFields(false); + setupPanel(entry, false); + }); gridPane.add(hideDefaultOwnerCommentButton, 1, gridPane.getRowCount(), 2, 1); setCompressedRowLayout(); } diff --git a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index 45248e89dfe0..dd1f417ce077 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -1,11 +1,6 @@ package org.jabref.gui.entryeditor; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.SequencedSet; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; import javafx.scene.control.Tooltip; @@ -29,37 +24,64 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.search.SearchQuery; -import com.tobiasdiez.easybind.EasyBind; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.SequencedSet; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; public class DeprecatedFieldsTab extends FieldsEditorTab { public static final String NAME = "Deprecated fields"; private final BibEntryTypesManager entryTypesManager; - public DeprecatedFieldsTab(BibDatabaseContext databaseContext, - SuggestionProviders suggestionProviders, - UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - DialogService dialogService, - GuiPreferences preferences, - ThemeManager themeManager, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, - OptionalObjectProperty searchQueryProperty) { - super(false, databaseContext, suggestionProviders, undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, luceneManager, searchQueryProperty); + public DeprecatedFieldsTab( + BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + UndoAction undoAction, + RedoAction redoAction, + DialogService dialogService, + GuiPreferences preferences, + ThemeManager themeManager, + BibEntryTypesManager entryTypesManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository, + LuceneManager luceneManager, + OptionalObjectProperty searchQueryProperty) { + super( + false, + databaseContext, + suggestionProviders, + undoManager, + undoAction, + redoAction, + dialogService, + preferences, + themeManager, + taskExecutor, + journalAbbreviationRepository, + luceneManager, + searchQueryProperty); 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 Cleanup Entries functionality to convert the entry to biblatex."))); - } else { - setTooltip(new Tooltip(Localization.lang("Shows fields having a successor in biblatex."))); - } - }); + 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 Cleanup Entries functionality to convert the entry to biblatex."))); + } else { + setTooltip( + new Tooltip( + Localization.lang( + "Shows fields having a successor in biblatex."))); + } + }); setGraphic(IconTheme.JabRefIcons.OPTIONAL.getGraphicNode()); } @@ -68,7 +90,9 @@ protected SequencedSet determineFieldsToShow(BibEntry entry) { BibDatabaseMode mode = databaseContext.getMode(); 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)); + 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/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java index 4458d80a405d..5e25f7244bd2 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java @@ -1,7 +1,5 @@ package org.jabref.gui.entryeditor; -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.preferences.GuiPreferences; @@ -17,23 +15,26 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.search.SearchQuery; +import javax.swing.undo.UndoManager; + public class DetailOptionalFieldsTab extends OptionalFieldsTabBase { public static final String NAME = "Optional fields 2"; - public DetailOptionalFieldsTab(BibDatabaseContext databaseContext, - SuggestionProviders suggestionProviders, - UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - DialogService dialogService, - GuiPreferences preferences, - ThemeManager themeManager, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, - OptionalObjectProperty searchQueryProperty) { + public DetailOptionalFieldsTab( + BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + UndoAction undoAction, + RedoAction redoAction, + DialogService dialogService, + GuiPreferences preferences, + ThemeManager themeManager, + BibEntryTypesManager entryTypesManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository, + LuceneManager luceneManager, + OptionalObjectProperty searchQueryProperty) { super( Localization.lang("Optional fields 2"), false, @@ -49,7 +50,6 @@ public DetailOptionalFieldsTab(BibDatabaseContext databaseContext, taskExecutor, journalAbbreviationRepository, luceneManager, - searchQueryProperty - ); + searchQueryProperty); } } diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 88c282366f28..ad539a224b59 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -1,18 +1,10 @@ package org.jabref.gui.entryeditor; -import java.io.File; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.SortedSet; -import java.util.stream.Collectors; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.geometry.Side; @@ -64,14 +56,23 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.Subscription; -import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.stream.Collectors; + /** * 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) @@ -131,28 +132,38 @@ public EntryEditor(LibraryTab libraryTab, UndoAction undoAction, RedoAction redo this.undoAction = undoAction; this.redoAction = redoAction; - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); this.entryEditorPreferences = preferences.getEntryEditorPreferences(); - this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), databaseContext, dialogService); + this.fileLinker = + new ExternalFilesEntryLinker( + preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), + databaseContext, + dialogService); setupKeyBindings(); this.allPossibleTabs = createTabs(); - this.previewTabs = this.allPossibleTabs.stream().filter(OffersPreview.class::isInstance).map(OffersPreview.class::cast).toList(); + this.previewTabs = + this.allPossibleTabs.stream() + .filter(OffersPreview.class::isInstance) + .map(OffersPreview.class::cast) + .toList(); setupDragAndDrop(libraryTab); - EasyBind.subscribe(tabbed.getSelectionModel().selectedItemProperty(), tab -> { - EntryEditorTab activeTab = (EntryEditorTab) tab; - if (activeTab != null) { - activeTab.notifyAboutFocus(currentlyEditedEntry); - } - }); + EasyBind.subscribe( + tabbed.getSelectionModel().selectedItemProperty(), + tab -> { + EntryEditorTab activeTab = (EntryEditorTab) tab; + if (activeTab != null) { + activeTab.notifyAboutFocus(currentlyEditedEntry); + } + }); - EasyBind.listen(preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(), + EasyBind.listen( + preferences.getPreviewPreferences().showPreviewAsExtraTabProperty(), (obs, oldValue, newValue) -> { if (currentlyEditedEntry != null) { adaptVisibleTabs(); @@ -161,85 +172,99 @@ public EntryEditor(LibraryTab libraryTab, UndoAction undoAction, RedoAction redo } private void setupDragAndDrop(LibraryTab libraryTab) { - this.setOnDragOver(event -> { - if (event.getDragboard().hasFiles()) { - event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); - } - event.consume(); - }); - - this.setOnDragDropped(event -> { - BibEntry entry = this.getCurrentlyEditedEntry(); - boolean success = false; - - if (event.getDragboard().hasContent(DataFormat.FILES)) { - List draggedFiles = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); - switch (event.getTransferMode()) { - case COPY -> { - LOGGER.debug("Mode COPY"); - fileLinker.copyFilesToFileDirAndAddToEntry(entry, draggedFiles, libraryTab.getLuceneManager()); - } - case MOVE -> { - LOGGER.debug("Mode MOVE"); - fileLinker.moveFilesToFileDirRenameAndAddToEntry(entry, draggedFiles, libraryTab.getLuceneManager()); + this.setOnDragOver( + event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes( + TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); } - case LINK -> { - LOGGER.debug("Mode LINK"); - fileLinker.addFilesToEntry(entry, draggedFiles); + event.consume(); + }); + + this.setOnDragDropped( + event -> { + BibEntry entry = this.getCurrentlyEditedEntry(); + boolean success = false; + + if (event.getDragboard().hasContent(DataFormat.FILES)) { + List draggedFiles = + event.getDragboard().getFiles().stream() + .map(File::toPath) + .collect(Collectors.toList()); + switch (event.getTransferMode()) { + case COPY -> { + LOGGER.debug("Mode COPY"); + fileLinker.copyFilesToFileDirAndAddToEntry( + entry, draggedFiles, libraryTab.getLuceneManager()); + } + case MOVE -> { + LOGGER.debug("Mode MOVE"); + fileLinker.moveFilesToFileDirRenameAndAddToEntry( + entry, draggedFiles, libraryTab.getLuceneManager()); + } + case LINK -> { + LOGGER.debug("Mode LINK"); + fileLinker.addFilesToEntry(entry, draggedFiles); + } + } + success = true; } - } - success = true; - } - event.setDropCompleted(success); - event.consume(); - }); + event.setDropCompleted(success); + event.consume(); + }); } /** * Set up key bindings specific for the entry editor. */ private void setupKeyBindings() { - this.addEventHandler(KeyEvent.KEY_PRESSED, event -> { - Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); - if (keyBinding.isPresent()) { - switch (keyBinding.get()) { - case ENTRY_EDITOR_NEXT_PANEL: - case ENTRY_EDITOR_NEXT_PANEL_2: - tabbed.getSelectionModel().selectNext(); - event.consume(); - break; - case ENTRY_EDITOR_PREVIOUS_PANEL: - case ENTRY_EDITOR_PREVIOUS_PANEL_2: - tabbed.getSelectionModel().selectPrevious(); - event.consume(); - break; - case ENTRY_EDITOR_NEXT_ENTRY: - libraryTab.selectNextEntry(); - event.consume(); - break; - case ENTRY_EDITOR_PREVIOUS_ENTRY: - libraryTab.selectPreviousEntry(); - event.consume(); - break; - case HELP: - new HelpAction(HelpFile.ENTRY_EDITOR, dialogService, preferences.getExternalApplicationsPreferences()).execute(); - event.consume(); - break; - case CLOSE: - // We do not want to close the entry editor as such - // We just want to unfocus the field - tabbed.requestFocus(); - break; - case OPEN_CLOSE_ENTRY_EDITOR: - close(); - event.consume(); - break; - default: - // Pass other keys to parent - } - } - }); + this.addEventHandler( + KeyEvent.KEY_PRESSED, + event -> { + Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); + if (keyBinding.isPresent()) { + switch (keyBinding.get()) { + case ENTRY_EDITOR_NEXT_PANEL: + case ENTRY_EDITOR_NEXT_PANEL_2: + tabbed.getSelectionModel().selectNext(); + event.consume(); + break; + case ENTRY_EDITOR_PREVIOUS_PANEL: + case ENTRY_EDITOR_PREVIOUS_PANEL_2: + tabbed.getSelectionModel().selectPrevious(); + event.consume(); + break; + case ENTRY_EDITOR_NEXT_ENTRY: + libraryTab.selectNextEntry(); + event.consume(); + break; + case ENTRY_EDITOR_PREVIOUS_ENTRY: + libraryTab.selectPreviousEntry(); + event.consume(); + break; + case HELP: + new HelpAction( + HelpFile.ENTRY_EDITOR, + dialogService, + preferences.getExternalApplicationsPreferences()) + .execute(); + event.consume(); + break; + case CLOSE: + // We do not want to close the entry editor as such + // We just want to unfocus the field + tabbed.requestFocus(); + break; + case OPEN_CLOSE_ENTRY_EDITOR: + close(); + event.consume(); + break; + default: + // Pass other keys to parent + } + } + }); } @FXML @@ -254,14 +279,25 @@ private void deleteEntry() { @FXML void generateCiteKeyButton() { - GenerateCitationKeySingleAction action = new GenerateCitationKeySingleAction(getCurrentlyEditedEntry(), databaseContext, - dialogService, preferences, undoManager); + GenerateCitationKeySingleAction action = + new GenerateCitationKeySingleAction( + getCurrentlyEditedEntry(), + databaseContext, + dialogService, + preferences, + undoManager); action.execute(); } @FXML void generateCleanupButton() { - CleanupSingleAction action = new CleanupSingleAction(getCurrentlyEditedEntry(), preferences, dialogService, stateManager, undoManager); + CleanupSingleAction action = + new CleanupSingleAction( + getCurrentlyEditedEntry(), + preferences, + dialogService, + stateManager, + undoManager); action.execute(); } @@ -278,44 +314,180 @@ private void navigateToNextEntry() { private List createTabs() { List tabs = new LinkedList<>(); - tabs.add(new PreviewTab(databaseContext, dialogService, preferences, themeManager, taskExecutor, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add( + new PreviewTab( + databaseContext, + dialogService, + preferences, + themeManager, + taskExecutor, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); // Required, optional (important+detail), deprecated, and "other" fields - tabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new ImportantOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new DetailOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); - tabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, bibEntryTypesManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add( + new RequiredFieldsTab( + databaseContext, + libraryTab.getSuggestionProviders(), + undoManager, + undoAction, + redoAction, + dialogService, + preferences, + themeManager, + bibEntryTypesManager, + taskExecutor, + journalAbbreviationRepository, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); + tabs.add( + new ImportantOptionalFieldsTab( + databaseContext, + libraryTab.getSuggestionProviders(), + undoManager, + undoAction, + redoAction, + dialogService, + preferences, + themeManager, + bibEntryTypesManager, + taskExecutor, + journalAbbreviationRepository, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); + tabs.add( + new DetailOptionalFieldsTab( + databaseContext, + libraryTab.getSuggestionProviders(), + undoManager, + undoAction, + redoAction, + dialogService, + preferences, + themeManager, + bibEntryTypesManager, + taskExecutor, + journalAbbreviationRepository, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); + tabs.add( + new DeprecatedFieldsTab( + databaseContext, + libraryTab.getSuggestionProviders(), + undoManager, + undoAction, + redoAction, + dialogService, + preferences, + themeManager, + bibEntryTypesManager, + taskExecutor, + journalAbbreviationRepository, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); + tabs.add( + new OtherFieldsTab( + databaseContext, + libraryTab.getSuggestionProviders(), + undoManager, + undoAction, + redoAction, + dialogService, + preferences, + themeManager, + bibEntryTypesManager, + taskExecutor, + journalAbbreviationRepository, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); // Comment Tab: Tab for general and user-specific comments - tabs.add(new CommentsTab(preferences, databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, themeManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add( + new CommentsTab( + preferences, + databaseContext, + libraryTab.getSuggestionProviders(), + undoManager, + undoAction, + redoAction, + dialogService, + themeManager, + taskExecutor, + journalAbbreviationRepository, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); Map> entryEditorTabList = getAdditionalUserConfiguredTabs(); for (Map.Entry> tab : entryEditorTabList.entrySet()) { - tabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, undoAction, redoAction, dialogService, preferences, themeManager, taskExecutor, journalAbbreviationRepository, libraryTab.getLuceneManager(), libraryTab.searchQueryProperty())); + tabs.add( + new UserDefinedFieldsTab( + tab.getKey(), + tab.getValue(), + databaseContext, + libraryTab.getSuggestionProviders(), + undoManager, + undoAction, + redoAction, + dialogService, + preferences, + themeManager, + taskExecutor, + journalAbbreviationRepository, + libraryTab.getLuceneManager(), + libraryTab.searchQueryProperty())); } tabs.add(new MathSciNetTab()); tabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); tabs.add(new SciteTab(preferences, taskExecutor, dialogService)); - tabs.add(new CitationRelationsTab(dialogService, databaseContext, - undoManager, stateManager, fileMonitor, preferences, libraryTab, taskExecutor, bibEntryTypesManager)); - tabs.add(new RelatedArticlesTab(buildInfo, databaseContext, preferences, dialogService, taskExecutor)); - sourceTab = new SourceTab( - databaseContext, - undoManager, - preferences.getFieldPreferences(), - preferences.getImportFormatPreferences(), - fileMonitor, - dialogService, - bibEntryTypesManager, - keyBindingRepository, - libraryTab.searchQueryProperty()); + tabs.add( + new CitationRelationsTab( + dialogService, + databaseContext, + undoManager, + stateManager, + fileMonitor, + preferences, + libraryTab, + taskExecutor, + bibEntryTypesManager)); + tabs.add( + new RelatedArticlesTab( + buildInfo, databaseContext, preferences, dialogService, taskExecutor)); + sourceTab = + new SourceTab( + databaseContext, + undoManager, + preferences.getFieldPreferences(), + preferences.getImportFormatPreferences(), + fileMonitor, + dialogService, + bibEntryTypesManager, + keyBindingRepository, + libraryTab.searchQueryProperty()); tabs.add(sourceTab); - tabs.add(new LatexCitationsTab(databaseContext, preferences, dialogService, directoryMonitorManager)); - tabs.add(new FulltextSearchResultsTab(stateManager, preferences, dialogService, databaseContext, taskExecutor, libraryTab.searchQueryProperty())); - tabs.add(new AiSummaryTab(libraryTab.getBibDatabaseContext(), aiService, dialogService, preferences)); - tabs.add(new AiChatTab(libraryTab.getBibDatabaseContext(), aiService, chatHistoryService, dialogService, preferences, taskExecutor)); + tabs.add( + new LatexCitationsTab( + databaseContext, preferences, dialogService, directoryMonitorManager)); + tabs.add( + new FulltextSearchResultsTab( + stateManager, + preferences, + dialogService, + databaseContext, + taskExecutor, + libraryTab.searchQueryProperty())); + tabs.add( + new AiSummaryTab( + libraryTab.getBibDatabaseContext(), aiService, dialogService, preferences)); + tabs.add( + new AiChatTab( + libraryTab.getBibDatabaseContext(), + aiService, + chatHistoryService, + dialogService, + preferences, + taskExecutor)); return tabs; } @@ -328,9 +500,11 @@ private List createTabs() { * @return Map of tab names and the fields to show in them. */ private Map> getAdditionalUserConfiguredTabs() { - Map> entryEditorTabList = new HashMap<>(entryEditorPreferences.getEntryEditorTabs()); + Map> entryEditorTabList = + new HashMap<>(entryEditorPreferences.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); @@ -339,7 +513,8 @@ 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); @@ -356,17 +531,27 @@ private Map> getAdditionalUserConfiguredTabs() { * Adapt the visible tabs to the current entry type. */ private 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 // 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; @@ -393,11 +578,17 @@ public void setCurrentlyEditedEntry(BibEntry currentlyEditedEntry) { // Remove subscription for old entry if existing typeSubscription.unsubscribe(); } - typeSubscription = EasyBind.subscribe(this.currentlyEditedEntry.typeProperty(), type -> { - typeLabel.setText(new TypedBibEntry(currentlyEditedEntry, databaseContext.getMode()).getTypeForDisplay()); - adaptVisibleTabs(); - getSelectedTab().notifyAboutFocus(currentlyEditedEntry); - }); + typeSubscription = + EasyBind.subscribe( + this.currentlyEditedEntry.typeProperty(), + type -> { + typeLabel.setText( + new TypedBibEntry( + currentlyEditedEntry, databaseContext.getMode()) + .getTypeForDisplay()); + adaptVisibleTabs(); + getSelectedTab().notifyAboutFocus(currentlyEditedEntry); + }); adaptVisibleTabs(); setupToolBar(); @@ -414,34 +605,46 @@ private EntryEditorTab getSelectedTab() { private void setupToolBar() { // Update type label - TypedBibEntry typedEntry = new TypedBibEntry(currentlyEditedEntry, databaseContext.getMode()); + TypedBibEntry typedEntry = + new TypedBibEntry(currentlyEditedEntry, databaseContext.getMode()); typeLabel.setText(typedEntry.getTypeForDisplay()); // Add type change menu - ContextMenu typeMenu = new ChangeEntryTypeMenu(Collections.singletonList(currentlyEditedEntry), databaseContext, undoManager, bibEntryTypesManager).asContextMenu(); + ContextMenu typeMenu = + new ChangeEntryTypeMenu( + Collections.singletonList(currentlyEditedEntry), + databaseContext, + undoManager, + bibEntryTypesManager) + .asContextMenu(); typeLabel.setOnMouseClicked(event -> typeMenu.show(typeLabel, Side.RIGHT, 0, 0)); - typeChangeButton.setOnMouseClicked(event -> typeMenu.show(typeChangeButton, 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(), - databaseContext); + SortedSet entryBasedFetchers = + WebFetchers.getEntryBasedFetchers( + preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), + preferences.getFilePreferences(), + databaseContext); 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 -> { - GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); - PdfMergeMetadataImporter.EntryBasedFetcherWrapper pdfMergeMetadataImporter = - new PdfMergeMetadataImporter.EntryBasedFetcherWrapper( - preferences.getImportFormatPreferences(), - preferences.getFilePreferences(), - databaseContext); - fetchAndMerge(pdfMergeMetadataImporter); - }); + fetcherMenuItem.setOnAction( + event -> { + GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided( + dialogService, preferences.getGrobidPreferences()); + PdfMergeMetadataImporter.EntryBasedFetcherWrapper + pdfMergeMetadataImporter = + new PdfMergeMetadataImporter.EntryBasedFetcherWrapper( + preferences.getImportFormatPreferences(), + preferences.getFilePreferences(), + databaseContext); + fetchAndMerge(pdfMergeMetadataImporter); + }); } else { fetcherMenuItem.setOnAction(event -> fetchAndMerge(fetcher)); } @@ -452,19 +655,26 @@ private void setupToolBar() { } private void fetchAndMerge(EntryBasedFetcher fetcher) { - new FetchAndMergeEntry(libraryTab.getBibDatabaseContext(), taskExecutor, preferences, dialogService, undoManager).fetchAndMerge(currentlyEditedEntry, fetcher); + new FetchAndMergeEntry( + libraryTab.getBibDatabaseContext(), + taskExecutor, + preferences, + dialogService, + undoManager) + .fetchAndMerge(currentlyEditedEntry, fetcher); } public void setFocusToField(Field field) { - UiTaskExecutor.runInJavaFXThread(() -> { - for (Tab tab : tabbed.getTabs()) { - if ((tab instanceof FieldsEditorTab fieldsEditorTab) - && fieldsEditorTab.getShownFields().contains(field)) { - tabbed.getSelectionModel().select(tab); - fieldsEditorTab.requestFocus(field); - } - } - }); + UiTaskExecutor.runInJavaFXThread( + () -> { + for (Tab tab : tabbed.getTabs()) { + if ((tab instanceof FieldsEditorTab fieldsEditorTab) + && fieldsEditorTab.getShownFields().contains(field)) { + tabbed.getSelectionModel().select(tab); + fieldsEditorTab.requestFocus(field); + } + } + }); } public void nextPreviewStyle() { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java index 04ce8173bc9b..03574e12d12b 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java @@ -1,8 +1,5 @@ package org.jabref.gui.entryeditor; -import java.util.Map; -import java.util.Set; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.MapProperty; @@ -16,6 +13,9 @@ import org.jabref.model.entry.field.Field; +import java.util.Map; +import java.util.Set; + public class EntryEditorPreferences { /** @@ -53,25 +53,28 @@ public static JournalPopupEnabled fromString(String status) { 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 showSourceTabByDefault, - boolean enableValidation, - boolean allowIntegerEditionBibtex, - double dividerPosition, - boolean autolinkFilesEnabled, - JournalPopupEnabled journalPopupEnabled, - boolean showSciteTab, - boolean showUserCommentsFields, - double previewWidthDividerPosition) { - - this.entryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(entryEditorTabList)); - this.defaultEntryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(defaultEntryEditorTabList)); + public EntryEditorPreferences( + Map> entryEditorTabList, + Map> defaultEntryEditorTabList, + boolean shouldOpenOnNewEntry, + boolean shouldShowRecommendationsTab, + boolean shouldShowAiSummaryTab, + boolean shouldShowAiChatTab, + boolean shouldShowLatexCitationsTab, + boolean showSourceTabByDefault, + boolean enableValidation, + boolean allowIntegerEditionBibtex, + double dividerPosition, + 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.shouldOpenOnNewEntry = new SimpleBooleanProperty(shouldOpenOnNewEntry); this.shouldShowRecommendationsTab = new SimpleBooleanProperty(shouldShowRecommendationsTab); this.shouldShowAiSummaryTab = new SimpleBooleanProperty(shouldShowAiSummaryTab); diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 41b29ee8f421..2b0263bee296 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -1,15 +1,7 @@ package org.jabref.gui.entryeditor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.SequencedSet; -import java.util.stream.Stream; - -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; import javafx.collections.ObservableList; import javafx.geometry.VPos; @@ -43,8 +35,16 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.search.SearchQuery; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.Subscription; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SequencedSet; +import java.util.stream.Stream; + +import javax.swing.undo.UndoManager; /** * A single tab displayed in the EntryEditor holding several FieldEditors. @@ -67,22 +67,24 @@ abstract class FieldsEditorTab extends EntryEditorTab implements OffersPreview { private final LuceneManager luceneManager; private final OptionalObjectProperty searchQueryProperty; private Collection fields = new ArrayList<>(); + @SuppressWarnings("FieldCanBeLocal") private Subscription dividerPositionSubscription; - public FieldsEditorTab(boolean compressed, - BibDatabaseContext databaseContext, - SuggestionProviders suggestionProviders, - UndoManager undoManager, - UndoAction undoAction, - RedoAction redoAction, - DialogService dialogService, - GuiPreferences preferences, - ThemeManager themeManager, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository, - LuceneManager luceneManager, - OptionalObjectProperty searchQueryProperty) { + public FieldsEditorTab( + boolean compressed, + BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + UndoAction undoAction, + RedoAction redoAction, + DialogService dialogService, + GuiPreferences preferences, + ThemeManager themeManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository, + LuceneManager luceneManager, + OptionalObjectProperty searchQueryProperty) { this.isCompressed = compressed; this.databaseContext = Objects.requireNonNull(databaseContext); this.suggestionProviders = Objects.requireNonNull(suggestionProviders); @@ -120,10 +122,8 @@ protected void setupPanel(BibEntry entry, boolean compressed) { fields = determineFieldsToShow(entry); - List

\n" + "" + "Detail information:" + "\n\n```\n" - + getLogMessagesAsString(allMessagesData) + "\n```\n\n
"; + String issueDetails = + "
\n" + + "" + + "Detail information:" + + "\n\n```\n" + + getLogMessagesAsString(allMessagesData) + + "\n```\n\n
"; clipBoardManager.setContent(issueDetails); // Bug report body - String issueBody = systemInfo + "\n\n" + howToReproduce + "\n\n" + "Paste your log details here."; + String issueBody = + systemInfo + "\n\n" + howToReproduce + "\n\n" + "Paste your log details here."; 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); - NativeDesktop.openBrowser(uriBuilder.build().toString(), preferences.getExternalApplicationsPreferences()); + 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); + NativeDesktop.openBrowser( + uriBuilder.build().toString(), + preferences.getExternalApplicationsPreferences()); } catch (IOException | URISyntaxException e) { LOGGER.error("Problem opening url", e); } diff --git a/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java b/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java index 378317147afd..f5c07b758d5a 100644 --- a/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java +++ b/src/main/java/org/jabref/gui/errorconsole/LogEventViewModel.java @@ -1,15 +1,15 @@ package org.jabref.gui.errorconsole; -import java.util.Objects; -import java.util.Optional; +import com.google.common.base.Throwables; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; import org.jabref.logic.os.OS; - -import com.google.common.base.Throwables; import org.tinylog.core.LogEntry; +import java.util.Objects; +import java.util.Optional; + public class LogEventViewModel { private final LogEntry logEvent; @@ -51,6 +51,7 @@ public Optional getStackTrace() { } public String getDetailedText() { - return getDisplayText() + getStackTrace().map(stacktrace -> OS.NEWLINE + stacktrace).orElse(""); + return getDisplayText() + + getStackTrace().map(stacktrace -> OS.NEWLINE + stacktrace).orElse(""); } } diff --git a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java index dc7f27da2c3d..92bddcb90918 100644 --- a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java +++ b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogView.java @@ -1,5 +1,9 @@ package org.jabref.gui.exporter; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.ButtonType; @@ -10,9 +14,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.preferences.CliPreferences; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - public class CreateModifyExporterDialogView extends BaseDialog { private final ExporterViewModel exporter; @@ -28,17 +29,16 @@ public CreateModifyExporterDialogView(ExporterViewModel exporter) { this.setTitle(Localization.lang("Customize Export Formats")); this.exporter = exporter; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); - - this.setResultConverter(button -> { - if (button == saveExporter) { - return viewModel.saveExporter(); - } else { - return null; - } - }); + ViewLoader.view(this).load().setAsDialogPane(this); + + this.setResultConverter( + button -> { + if (button == saveExporter) { + return viewModel.saveExporter(); + } else { + return null; + } + }); } @FXML diff --git a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java index 57613bd8af7c..fecc84f40c5b 100644 --- a/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java +++ b/src/main/java/org/jabref/gui/exporter/CreateModifyExporterDialogViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.exporter; -import java.nio.file.Path; - import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -12,20 +10,21 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.StandardFileType; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; + /** * 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 { - private static final Logger LOGGER = LoggerFactory.getLogger(CreateModifyExporterDialogViewModel.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(CreateModifyExporterDialogViewModel.class); private final DialogService dialogService; private final CliPreferences preferences; @@ -34,9 +33,8 @@ public class CreateModifyExporterDialogViewModel extends AbstractViewModel { 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; @@ -55,33 +53,46 @@ public ExporterViewModel saveExporter() { } // Check that there are no empty strings. - if (layoutFile.get().isEmpty() || name.get().isEmpty() || extension.get().isEmpty() + if (layoutFile.get().isEmpty() + || name.get().isEmpty() + || extension.get().isEmpty() || !layoutFile.get().endsWith(".layout")) { LOGGER.info("One of the fields is empty or invalid."); return null; } - // 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()); + // 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); return new ExporterViewModel(format); } public void browse() { - String fileDir = layoutFile.getValue().isEmpty() - ? 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())); + String fileDir = + layoutFile.getValue().isEmpty() + ? 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())); } public StringProperty getName() { diff --git a/src/main/java/org/jabref/gui/exporter/ExportCommand.java b/src/main/java/org/jabref/gui/exporter/ExportCommand.java index 4cadba61f5bf..4e8771b12d3e 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportCommand.java +++ b/src/main/java/org/jabref/gui/exporter/ExportCommand.java @@ -1,16 +1,9 @@ package org.jabref.gui.exporter; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Collectors; - import javafx.stage.FileChooser; import javafx.util.Duration; +import org.controlsfx.control.action.Action; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -31,17 +24,26 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; - -import org.controlsfx.control.action.Action; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + /** * Performs an export action */ 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); @@ -54,14 +56,15 @@ public enum ExportMethod { EXPORT_ALL, EXPORT_SELECTED } 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,52 +74,74 @@ public ExportCommand(ExportMethod exportMethod, this.abbreviationRepository = abbreviationRepository; this.taskExecutor = taskExecutor; - this.executable.bind(exportMethod == ExportMethod.EXPORT_SELECTED - ? ActionHelper.needsEntriesSelected(stateManager) - : ActionHelper.needsDatabase(stateManager)); + this.executable.bind( + exportMethod == ExportMethod.EXPORT_SELECTED + ? 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, - entryTypesManager); - 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(); - dialogService.showFileSaveDialog(fileDialogConfiguration) - .ifPresent(path -> export(path, fileDialogConfiguration.getSelectedExtensionFilter(), exporters)); + ExporterFactory exporterFactory = ExporterFactory.create(preferences, entryTypesManager); + 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(); + dialogService + .showFileSaveDialog(fileDialogConfiguration) + .ifPresent( + path -> + export( + path, + fileDialogConfiguration.getSelectedExtensionFilter(), + exporters)); } - private void export(Path file, FileChooser.ExtensionFilter selectedExtensionFilter, List exporters) { - String selectedExtension = selectedExtensionFilter.getExtensions().getFirst().replace("*", ""); + private void export( + Path file, + FileChooser.ExtensionFilter selectedExtensionFilter, + List exporters) { + String selectedExtension = + selectedExtensionFilter.getExtensions().getFirst().replace("*", ""); if (!file.endsWith(selectedExtension)) { FileUtil.addExtension(file, selectedExtension); } - final Exporter format = FileFilterConverter.getExporter(selectedExtensionFilter, exporters) - .orElseThrow(() -> new IllegalStateException("User didn't selected a file type for the extension")); + final Exporter format = + FileFilterConverter.getExporter(selectedExtensionFilter, exporters) + .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 { // All entries - entries = stateManager.getActiveDatabase() - .map(BibDatabaseContext::getEntries) - .orElse(Collections.emptyList()); + entries = + stateManager + .getActiveDatabase() + .map(BibDatabaseContext::getEntries) + .orElse(Collections.emptyList()); } - List fileDirForDatabase = stateManager.getActiveDatabase() - .map(db -> db.getFileDirectories(preferences.getFilePreferences())) - .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory())); + List fileDirForDatabase = + stateManager + .getActiveDatabase() + .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: @@ -125,30 +150,43 @@ 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)); - }) + 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); } @@ -157,6 +195,7 @@ 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/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java index 5fb8cb1fe0c4..3b0ad13cd533 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java +++ b/src/main/java/org/jabref/gui/exporter/ExportToClipboardAction.java @@ -1,14 +1,6 @@ package org.jabref.gui.exporter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; +import com.airhacks.afterburner.injection.Injector; import javafx.scene.input.ClipboardContent; @@ -28,24 +20,33 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + 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, - StandardFileType.RIS); + 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<>(); @@ -54,11 +55,12 @@ public class ExportToClipboardAction extends SimpleCommand { 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; @@ -71,41 +73,62 @@ public ExportToClipboardAction(DialogService dialogService, @Override public void execute() { if (stateManager.getSelectedEntries().isEmpty()) { - dialogService.notify(Localization.lang("This operation requires one or more entries to be selected.")); + dialogService.notify( + Localization.lang( + "This operation requires one or more entries to be selected.")); return; } - ExporterFactory exporterFactory = ExporterFactory.create( - preferences, - Injector.instantiateModelOrService(BibEntryTypesManager.class)); - List exporters = exporterFactory.getExporters().stream() - .sorted(Comparator.comparing(Exporter::getName)) - .filter(exporter -> SUPPORTED_FILETYPES.contains(exporter.getFileType())) - .collect(Collectors.toList()); + ExporterFactory exporterFactory = + ExporterFactory.create( + preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); + 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); - - 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)); + Exporter defaultChoice = + exporters.stream() + .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); + + 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)); } private ExportResult exportToClipboard(Exporter exporter) throws Exception { - List fileDirForDatabase = stateManager.getActiveDatabase() - .map(db -> db.getFileDirectories(preferences.getFilePreferences())) - .orElse(List.of(preferences.getFilePreferences().getWorkingDirectory())); + List fileDirForDatabase = + stateManager + .getActiveDatabase() + .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()); @@ -153,9 +176,9 @@ private void setContentToClipboard(ExportResult result) { clipboardContent.putString(result.content); this.clipBoardManager.setContent(clipboardContent); - dialogService.notify(Localization.lang("Entries exported to clipboard") + ": " + entries.size()); + dialogService.notify( + Localization.lang("Entries exported to clipboard") + ": " + entries.size()); } - private record ExportResult(String content, FileType fileType) { - } + private record ExportResult(String content, FileType fileType) {} } diff --git a/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java b/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java index 23b92f010834..e881d1bf33c4 100644 --- a/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java +++ b/src/main/java/org/jabref/gui/exporter/ExporterViewModel.java @@ -8,7 +8,6 @@ /** * ExporterViewModel wraps a TemplateExporter from logic and is used in the exporter customization dialog view and ViewModel. */ - public class ExporterViewModel { private final TemplateExporter exporter; @@ -20,7 +19,8 @@ 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); } diff --git a/src/main/java/org/jabref/gui/exporter/SaveAction.java b/src/main/java/org/jabref/gui/exporter/SaveAction.java index 04b12fd88928..3119500bc0a8 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveAction.java @@ -1,6 +1,6 @@ package org.jabref.gui.exporter; -import java.util.function.Supplier; +import com.airhacks.afterburner.injection.Injector; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -10,14 +10,18 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.model.entry.BibEntryTypesManager; -import com.airhacks.afterburner.injection.Injector; +import java.util.function.Supplier; /** * This class is just a simple wrapper for the soon to be refactored SaveDatabaseAction. */ 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; @@ -25,11 +29,12 @@ public enum SaveMethod { SAVE, SAVE_AS, SAVE_SELECTED } private final DialogService dialogService; private final GuiPreferences preferences; - 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; @@ -44,11 +49,12 @@ public SaveAction(SaveMethod saveMethod, @Override public void execute() { - SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction( - tabSupplier.get(), - dialogService, - preferences, - Injector.instantiateModelOrService(BibEntryTypesManager.class)); + SaveDatabaseAction saveDatabaseAction = + new SaveDatabaseAction( + tabSupplier.get(), + dialogService, + preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); switch (saveMethod) { case SAVE -> saveDatabaseAction.save(); diff --git a/src/main/java/org/jabref/gui/exporter/SaveAllAction.java b/src/main/java/org/jabref/gui/exporter/SaveAllAction.java index a31329039bec..430b2cd40269 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveAllAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveAllAction.java @@ -1,7 +1,6 @@ package org.jabref.gui.exporter; -import java.util.List; -import java.util.function.Supplier; +import com.airhacks.afterburner.injection.Injector; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -10,7 +9,8 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntryTypesManager; -import com.airhacks.afterburner.injection.Injector; +import java.util.List; +import java.util.function.Supplier; public class SaveAllAction extends SimpleCommand { @@ -18,7 +18,10 @@ public class SaveAllAction extends SimpleCommand { private final DialogService dialogService; private final GuiPreferences preferences; - public SaveAllAction(Supplier> tabsSupplier, GuiPreferences preferences, DialogService dialogService) { + public SaveAllAction( + Supplier> tabsSupplier, + GuiPreferences preferences, + DialogService dialogService) { this.tabsSupplier = tabsSupplier; this.dialogService = dialogService; this.preferences = preferences; @@ -29,7 +32,12 @@ 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)); + SaveDatabaseAction saveDatabaseAction = + new SaveDatabaseAction( + libraryTab, + dialogService, + preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); boolean saveResult = saveDatabaseAction.save(); if (!saveResult) { dialogService.notify(Localization.lang("Could not save file.")); diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 6a2dd9cb42b1..ed51ae36e3de 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -1,16 +1,5 @@ package org.jabref.gui.exporter; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.charset.UnsupportedCharsetException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.DialogPane; @@ -43,10 +32,20 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + /** * 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. @@ -63,13 +62,15 @@ public class SaveDatabaseAction { private final BibEntryTypesManager entryTypesManager; public enum SaveDatabaseMode { - SILENT, NORMAL + SILENT, + NORMAL } - public SaveDatabaseAction(LibraryTab libraryTab, - DialogService dialogService, - GuiPreferences preferences, - BibEntryTypesManager entryTypesManager) { + public SaveDatabaseAction( + LibraryTab libraryTab, + DialogService dialogService, + GuiPreferences preferences, + BibEntryTypesManager entryTypesManager) { this.libraryTab = libraryTab; this.dialogService = dialogService; this.preferences = preferences; @@ -96,37 +97,60 @@ 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); - } - }) + 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()); - preferences.getLastFilesOpenedPreferences().getFileHistory().newFile(path); - dialogService.notify(Localization.lang("Saved selected to '%0'.", path.toString())); - } 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); - } - }); + askForSavePath() + .ifPresent( + path -> { + try { + 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) { + 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); + } + }); } /** @@ -141,7 +165,10 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { if (databasePath.isPresent()) { // Close AutosaveManager, BackupManager, and LuceneManager 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.closeLuceneManger(); } @@ -149,7 +176,8 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { if (context.getLocation() == DatabaseLocation.SHARED) { // 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); @@ -160,7 +188,8 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { context.setDatabasePath(file); libraryTab.updateTabTitle(false); - // Reset (here: uninstall and install again) AutosaveManager, BackupManager and LuceneManager for the new file name + // Reset (here: uninstall and install again) AutosaveManager, BackupManager and + // LuceneManager for the new file name libraryTab.resetChangeMonitor(); libraryTab.installAutosaveManagerAndBackupManager(); libraryTab.createLuceneManager(); @@ -177,20 +206,26 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { * @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(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .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())); + selectedPath.ifPresent( + path -> preferences.getFilePreferences().setWorkingDirectory(path.getParent())); if (selectedPath.isPresent()) { Path savePath = selectedPath.get(); // Workaround for linux systems not adding file extension if (!savePath.getFileName().toString().toLowerCase().endsWith(".bib")) { savePath = Path.of(savePath.toString() + ".bib"); if (!Files.notExists(savePath)) { - if (!dialogService.showConfirmationDialogAndWait(Localization.lang("Overwrite file"), Localization.lang("'%0' exists. Overwrite file?", savePath.getFileName()))) { + if (!dialogService.showConfirmationDialogAndWait( + Localization.lang("Overwrite file"), + Localization.lang( + "'%0' exists. Overwrite file?", savePath.getFileName()))) { return Optional.empty(); } } @@ -211,7 +246,8 @@ private boolean save(BibDatabaseContext bibDatabaseContext, SaveDatabaseMode mod } private boolean save(Path targetPath, SaveDatabaseMode mode) { - if (mode == SaveDatabaseMode.NORMAL && libraryTab.getBibDatabaseContext().getEntries().size() > 2_000) { + if (mode == SaveDatabaseMode.NORMAL + && libraryTab.getBibDatabaseContext().getEntries().size() > 2_000) { dialogService.notify("%s...".formatted(Localization.lang("Saving library"))); } @@ -224,15 +260,26 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { } try { - Charset encoding = libraryTab.getBibDatabaseContext() - .getMetaData() - .getEncoding() - .orElse(StandardCharsets.UTF_8); + Charset encoding = + libraryTab + .getBibDatabaseContext() + .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()); + libraryTab + .getBibDatabaseContext() + .getMetaData() + .setEncoding(encoding, ChangePropagation.DO_NOT_POST_EVENT); + + boolean success = + saveDatabase( + targetPath, + false, + encoding, + BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, + getSaveOrder()); if (success) { libraryTab.getUndoManager().markUnchanged(); @@ -241,8 +288,12 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { dialogService.notify(Localization.lang("Library saved")); return success; } catch (SaveException ex) { - LOGGER.error("A problem occurred when trying to save the file %s".formatted(targetPath), ex); - dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + LOGGER.error( + "A problem occurred when trying to save the file %s".formatted(targetPath), ex); + dialogService.showErrorDialogAndWait( + Localization.lang("Save library"), + Localization.lang("Could not save file."), + ex); return false; } finally { // release panel from save status @@ -250,23 +301,39 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { } } - 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())) { - BibWriter bibWriter = new BibWriter(fileWriter, bibDatabaseContext.getDatabase().getNewLineSeparator()); - BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - entryTypesManager); + try (AtomicFileWriter fileWriter = + new AtomicFileWriter(file, encoding, saveConfiguration.shouldMakeBackup())) { + BibWriter bibWriter = + new BibWriter( + fileWriter, bibDatabaseContext.getDatabase().getNewLineSeparator()); + BibtexDatabaseWriter databaseWriter = + new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + preferences.getFieldPreferences(), + preferences.getCitationKeyPatternPreferences(), + entryTypesManager); if (selectedOnly) { - databaseWriter.savePartOfDatabase(bibDatabaseContext, libraryTab.getSelectedEntries()); + databaseWriter.savePartOfDatabase( + bibDatabaseContext, libraryTab.getSelectedEntries()); } else { databaseWriter.saveDatabase(bibDatabaseContext); } @@ -274,10 +341,20 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, 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); + throw new SaveException( + Localization.lang( + "Character encoding '%0' is not supported.", + encoding.displayName()), + ex); } catch (IOException ex) { throw new SaveException("Problems saving: " + ex, ex); } @@ -285,27 +362,56 @@ private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, } } - 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(); + boolean saveWithDifferentEncoding = + dialogService + .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, Encodings.getCharsets()); + Optional newEncoding = + dialogService.showChoiceDialogAndWait( + Localization.lang("Save library"), + Localization.lang("Select new encoding"), + Localization.lang("Save library"), + encoding, + Encodings.getCharsets()); 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/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java b/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java index 0b084517295e..8fe96ebb4b8c 100644 --- a/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java +++ b/src/main/java/org/jabref/gui/exporter/WriteMetadataToLinkedPdfsAction.java @@ -1,10 +1,6 @@ package org.jabref.gui.exporter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -21,17 +17,21 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; /** * 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 static final Logger LOGGER = + LoggerFactory.getLogger(WriteMetadataToLinkedPdfsAction.class); private final StateManager stateManager; private final BibEntryTypesManager entryTypesManager; @@ -42,14 +42,15 @@ public class WriteMetadataToLinkedPdfsAction extends SimpleCommand { 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; @@ -75,12 +76,16 @@ public void execute() { if (entries.isEmpty()) { LOGGER.warn("No entry selected for fulltext download."); - dialogService.notify(Localization.lang("This operation requires one or more entries to be selected.")); + dialogService.notify( + Localization.lang( + "This operation requires one or more entries to be selected.")); return; } else { - boolean confirm = dialogService.showConfirmationDialogAndWait( - Localization.lang("Write metadata to PDF files"), - Localization.lang("Write metadata for all PDFs in current library?")); + boolean confirm = + dialogService.showConfirmationDialogAndWait( + Localization.lang("Write metadata to PDF files"), + Localization.lang( + "Write metadata for all PDFs in current library?")); if (!confirm) { return; } @@ -90,15 +95,15 @@ public void execute() { dialogService.notify(Localization.lang("Writing metadata...")); new WriteMetaDataTask( - databaseContext, - entries, - abbreviationRepository, - entryTypesManager, - fieldPreferences, - filePreferences, - xmpPreferences, - stateManager, - dialogService) + databaseContext, + entries, + abbreviationRepository, + entryTypesManager, + fieldPreferences, + filePreferences, + xmpPreferences, + stateManager, + dialogService) .executeWith(taskExecutor); } @@ -119,15 +124,16 @@ private static class WriteMetaDataTask extends BackgroundTask { 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; @@ -152,19 +158,26 @@ public Void call() throws Exception { 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 '{}'", + LOGGER.debug( + "Skipped empty entry '{}'", entry.getCitationKey().orElse(entry.getAuthorTitleYear(16))); skipped++; } else { for (Path file : files) { - updateMessage(Localization.lang("Writing metadata to %0", file.getFileName())); + updateMessage( + Localization.lang("Writing metadata to %0", file.getFileName())); if (Files.exists(file)) { try { @@ -193,9 +206,16 @@ public Void call() throws Exception { } 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); diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java b/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java index d81a9cebcfa8..b3b6610af9ff 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoLinkFilesAction.java @@ -1,9 +1,7 @@ package org.jabref.gui.externalfiles; -import java.util.List; -import java.util.function.BiConsumer; - -import javax.swing.undo.UndoManager; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; import javafx.concurrent.Task; @@ -22,8 +20,10 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.StandardField; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; +import java.util.List; +import java.util.function.BiConsumer; + +import javax.swing.undo.UndoManager; /** * This Action may only be used in a menu or button. @@ -37,71 +37,100 @@ public class AutoLinkFilesAction extends SimpleCommand { 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; this.undoManager = undoManager; 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.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."))); } @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()); - 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 - String newVal = FileFieldWriter.getStringRepresentation(linkedFile); - String oldVal = entry.getField(StandardField.FILE).orElse(null); - UndoableFieldChange fieldChange = new UndoableFieldChange(entry, StandardField.FILE, oldVal, newVal); - nc.addEdit(fieldChange); // push to undo manager is in succeeded - entry.addFile(linkedFile); - }; - - @Override - protected AutoSetFileLinksUtil.LinkFilesResult call() { - return util.linkAssociatedFiles(entries, onLinkedFile); - } - - @Override - protected void succeeded() { - AutoSetFileLinksUtil.LinkFilesResult result = getValue(); - - if (!result.getFileExceptions().isEmpty()) { - dialogService.showWarningDialogAndWait( - Localization.lang("Automatically set file links"), - Localization.lang("Problem finding files. See error log for details.")); - return; - } - - if (result.getChangedEntries().isEmpty()) { - dialogService.showWarningDialogAndWait("Automatically set file links", - Localization.lang("Finished automatically setting external links.") + "\n" - + Localization.lang("No files found.")); - return; - } - - if (nc.hasEdits()) { - nc.end(); - undoManager.addEdit(nc); - } - - dialogService.notify(Localization.lang("Finished automatically setting external links.") + " " - + Localization.lang("Changed %0 entries.", String.valueOf(result.getChangedEntries().size()))); - } - }; + 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 + String newVal = FileFieldWriter.getStringRepresentation(linkedFile); + String oldVal = entry.getField(StandardField.FILE).orElse(null); + UndoableFieldChange fieldChange = + new UndoableFieldChange( + entry, StandardField.FILE, oldVal, newVal); + nc.addEdit(fieldChange); // push to undo manager is in succeeded + entry.addFile(linkedFile); + }; + + @Override + protected AutoSetFileLinksUtil.LinkFilesResult call() { + return util.linkAssociatedFiles(entries, onLinkedFile); + } + + @Override + protected void succeeded() { + AutoSetFileLinksUtil.LinkFilesResult result = getValue(); + + if (!result.getFileExceptions().isEmpty()) { + dialogService.showWarningDialogAndWait( + Localization.lang("Automatically set file links"), + Localization.lang( + "Problem finding files. See error log for details.")); + return; + } + + if (result.getChangedEntries().isEmpty()) { + dialogService.showWarningDialogAndWait( + "Automatically set file links", + Localization.lang( + "Finished automatically setting external links.") + + "\n" + + Localization.lang("No files found.")); + return; + } + + if (nc.hasEdits()) { + nc.end(); + undoManager.addEdit(nc); + } + + dialogService.notify( + Localization.lang("Finished automatically setting external links.") + + " " + + Localization.lang( + "Changed %0 entries.", + String.valueOf(result.getChangedEntries().size()))); + } + }; dialogService.showProgressDialog( Localization.lang("Automatically setting file links"), diff --git a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java index a5fd3c5050bc..62d05f151f2f 100644 --- a/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java +++ b/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java @@ -1,13 +1,5 @@ package org.jabref.gui.externalfiles; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.BiConsumer; - import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.externalfiletype.UnknownExternalFileType; @@ -20,10 +12,17 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; + public class AutoSetFileLinksUtil { public static class LinkFilesResult { @@ -54,21 +53,31 @@ public List getFileExceptions() { private final ExternalApplicationsPreferences externalApplicationsPreferences; private final FilePreferences filePreferences; - public AutoSetFileLinksUtil(BibDatabaseContext databaseContext, - ExternalApplicationsPreferences externalApplicationsPreferences, - FilePreferences filePreferences, - AutoLinkPreferences autoLinkPreferences) { - this(databaseContext.getFileDirectories(filePreferences), externalApplicationsPreferences, filePreferences, autoLinkPreferences); + public AutoSetFileLinksUtil( + BibDatabaseContext databaseContext, + ExternalApplicationsPreferences externalApplicationsPreferences, + FilePreferences filePreferences, + AutoLinkPreferences autoLinkPreferences) { + this( + databaseContext.getFileDirectories(filePreferences), + externalApplicationsPreferences, + filePreferences, + autoLinkPreferences); } - private AutoSetFileLinksUtil(List directories, ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, AutoLinkPreferences autoLinkPreferences) { + private AutoSetFileLinksUtil( + List directories, + ExternalApplicationsPreferences externalApplicationsPreferences, + FilePreferences filePreferences, + AutoLinkPreferences autoLinkPreferences) { this.directories = directories; this.autoLinkPreferences = autoLinkPreferences; this.externalApplicationsPreferences = externalApplicationsPreferences; this.filePreferences = filePreferences; } - public LinkFilesResult linkAssociatedFiles(List entries, BiConsumer onAddLinkedFile) { + public LinkFilesResult linkAssociatedFiles( + List entries, BiConsumer onAddLinkedFile) { LinkFilesResult result = new LinkFilesResult(); for (BibEntry entry : entries) { @@ -94,7 +103,10 @@ public LinkFilesResult linkAssociatedFiles(List 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); @@ -104,21 +116,28 @@ 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(file -> { - try { - return file.isPresent() && Files.isSameFile(file.get(), foundFile); - } catch (IOException e) { - LOGGER.error("Problem with isSameFile", e); - } - return false; - }); + boolean fileAlreadyLinked = + entry.getFiles().stream() + .map(file -> file.findIn(directories)) + .anyMatch( + file -> { + try { + return file.isPresent() + && Files.isSameFile(file.get(), foundFile); + } catch (IOException e) { + LOGGER.error("Problem with isSameFile", e); + } + return false; + }); if (!fileAlreadyLinked) { - Optional type = FileUtil.getFileExtension(foundFile) - .map(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, externalApplicationsPreferences)) - .orElse(Optional.of(new UnknownExternalFileType(""))); + Optional type = + FileUtil.getFileExtension(foundFile) + .map( + extension -> + ExternalFileTypes.getExternalFileTypeByExt( + extension, externalApplicationsPreferences)) + .orElse(Optional.of(new UnknownExternalFileType(""))); String strType = type.isPresent() ? type.get().getName() : ""; Path relativeFilePath = FileUtil.relativize(foundFile, directories); diff --git a/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java b/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java index 4162c9c674f6..2b93a0bba047 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java +++ b/src/main/java/org/jabref/gui/externalfiles/ChainedFilters.java @@ -1,13 +1,13 @@ package org.jabref.gui.externalfiles; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.util.Arrays; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Chains the given filters - if ALL of them accept, the result is also accepted */ @@ -23,13 +23,15 @@ public ChainedFilters(DirectoryStream.Filter... filters) { @Override public boolean accept(Path entry) throws IOException { - return Arrays.stream(filters).allMatch(filter -> { - try { - return filter.accept(entry); - } catch (IOException e) { - LOGGER.error("Could not apply filter", e); - return true; - } - }); + return Arrays.stream(filters) + .allMatch( + filter -> { + try { + return filter.accept(entry); + } catch (IOException e) { + LOGGER.error("Could not apply filter", e); + return true; + } + }); } } diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java index b0bdff13ed80..6d20da483bc2 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java @@ -1,11 +1,5 @@ package org.jabref.gui.externalfiles; -import java.net.URL; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - import javafx.concurrent.Task; import org.jabref.gui.DialogService; @@ -20,10 +14,15 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + /** * Try to download fulltext PDF for selected entry(ies) by following URL or DOI link. */ @@ -38,10 +37,11 @@ public class DownloadFullTextAction extends SimpleCommand { 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,16 +65,20 @@ 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 about to download full text documents for %0 entries.", - String.valueOf(stateManager.getSelectedEntries().size())) + "\n" - + Localization.lang("JabRef will send at least one request per entry to a publisher.") - + "\n" - + Localization.lang("Do you still want to continue?"), - Localization.lang("Download full text documents"), - Localization.lang("Cancel")); + boolean confirmDownload = + dialogService.showConfirmationDialogAndWait( + Localization.lang("Download full text documents"), + Localization.lang( + "You are about to download full text documents for %0 entries.", + String.valueOf( + stateManager.getSelectedEntries().size())) + + "\n" + + Localization.lang( + "JabRef will send at least one request per entry to a publisher.") + + "\n" + + Localization.lang("Do you still want to continue?"), + Localization.lang("Download full text documents"), + Localization.lang("Cancel")); if (!confirmDownload) { dialogService.notify(Localization.lang("Operation canceled.")); @@ -82,24 +86,29 @@ public void execute() { } } - Task>> findFullTextsTask = new Task<>() { - @Override - protected Map> call() { - Map> downloads = new ConcurrentHashMap<>(); - int count = 0; - for (BibEntry entry : entries) { - FulltextFetchers fetchers = new FulltextFetchers( - preferences.getImportFormatPreferences(), - preferences.getImporterPreferences()); - downloads.put(entry, fetchers.findFullTextPDF(entry)); - updateProgress(++count, entries.size()); - } - return downloads; - } - }; - - findFullTextsTask.setOnSucceeded(value -> - downloadFullTexts(findFullTextsTask.getValue(), stateManager.getActiveDatabase().get())); + Task>> findFullTextsTask = + new Task<>() { + @Override + protected Map> call() { + Map> downloads = new ConcurrentHashMap<>(); + int count = 0; + for (BibEntry entry : entries) { + FulltextFetchers fetchers = + new FulltextFetchers( + preferences.getImportFormatPreferences(), + preferences.getImporterPreferences()); + downloads.put(entry, fetchers.findFullTextPDF(entry)); + updateProgress(++count, entries.size()); + } + return downloads; + } + }; + + findFullTextsTask.setOnSucceeded( + value -> + downloadFullTexts( + findFullTextsTask.getValue(), + stateManager.getActiveDatabase().get())); dialogService.showProgressDialog( Localization.lang("Download full text documents"), @@ -109,15 +118,18 @@ protected Map> call() { taskExecutor.execute(findFullTextsTask); } - private void downloadFullTexts(Map> downloads, BibDatabaseContext databaseContext) { + private void downloadFullTexts( + Map> downloads, BibDatabaseContext databaseContext) { for (Map.Entry> download : downloads.entrySet()) { BibEntry entry = download.getKey(); Optional result = download.getValue(); if (result.isPresent()) { addLinkedFileFromURL(databaseContext, result.get(), entry); } else { - dialogService.notify(Localization.lang("No full text document found for entry %0.", - entry.getCitationKey().orElse(Localization.lang("undefined")))); + dialogService.notify( + Localization.lang( + "No full text document found for entry %0.", + entry.getCitationKey().orElse(Localization.lang("undefined")))); } } } @@ -134,17 +146,20 @@ private void addLinkedFileFromURL(BibDatabaseContext databaseContext, URL url, B 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 { - dialogService.notify(Localization.lang("Full text document for entry %0 already linked.", - entry.getCitationKey().orElse(Localization.lang("undefined")))); + dialogService.notify( + Localization.lang( + "Full text document for entry %0 already linked.", + entry.getCitationKey().orElse(Localization.lang("undefined")))); } } } diff --git a/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java b/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java index 8bfc842297da..a55b0821a156 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java +++ b/src/main/java/org/jabref/gui/externalfiles/DuplicateDecisionResult.java @@ -4,8 +4,4 @@ import org.jabref.model.entry.BibEntry; public record DuplicateDecisionResult( - DuplicateResolverDialog.DuplicateResolverResult decision, - BibEntry mergedEntry) { -} - - + DuplicateResolverDialog.DuplicateResolverResult decision, BibEntry mergedEntry) {} diff --git a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java index 85b1a83213e7..7214f7d811b1 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java +++ b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java @@ -1,13 +1,5 @@ package org.jabref.gui.externalfiles; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - import org.jabref.gui.DialogService; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; @@ -23,10 +15,17 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + public class ExternalFilesEntryLinker { private static final Logger LOGGER = LoggerFactory.getLogger(ExternalFilesEntryLinker.class); @@ -38,7 +37,11 @@ public class ExternalFilesEntryLinker { private final RenamePdfCleanup renameFilesCleanup; private final DialogService dialogService; - public ExternalFilesEntryLinker(ExternalApplicationsPreferences externalApplicationsPreferences, FilePreferences filePreferences, BibDatabaseContext bibDatabaseContext, DialogService dialogService) { + public ExternalFilesEntryLinker( + ExternalApplicationsPreferences externalApplicationsPreferences, + FilePreferences filePreferences, + BibDatabaseContext bibDatabaseContext, + DialogService dialogService) { this.externalApplicationsPreferences = externalApplicationsPreferences; this.filePreferences = filePreferences; this.bibDatabaseContext = bibDatabaseContext; @@ -48,7 +51,8 @@ public ExternalFilesEntryLinker(ExternalApplicationsPreferences externalApplicat } public Optional copyFileToFileDir(Path file) { - Optional firstExistingFileDir = bibDatabaseContext.getFirstExistingFileDir(filePreferences); + Optional firstExistingFileDir = + bibDatabaseContext.getFirstExistingFileDir(filePreferences); if (firstExistingFileDir.isPresent()) { Path targetFile = firstExistingFileDir.get().resolve(file.getFileName()); if (FileUtil.copyFile(file, targetFile, false)) { @@ -69,17 +73,25 @@ public void moveLinkedFilesToFileDir(BibEntry entry) { public void addFilesToEntry(BibEntry entry, List files) { List validFiles = getValidFileNames(files); for (Path file : validFiles) { - FileUtil.getFileExtension(file).ifPresent(ext -> { - ExternalFileType type = ExternalFileTypes.getExternalFileTypeByExt(ext, externalApplicationsPreferences) - .orElse(new UnknownExternalFileType(ext)); - Path relativePath = FileUtil.relativize(file, bibDatabaseContext, filePreferences); - LinkedFile linkedfile = new LinkedFile("", relativePath, type.getName()); - entry.addFile(linkedfile); - }); + FileUtil.getFileExtension(file) + .ifPresent( + ext -> { + ExternalFileType type = + ExternalFileTypes.getExternalFileTypeByExt( + ext, externalApplicationsPreferences) + .orElse(new UnknownExternalFileType(ext)); + Path relativePath = + FileUtil.relativize( + file, bibDatabaseContext, filePreferences); + LinkedFile linkedfile = + new LinkedFile("", relativePath, type.getName()); + entry.addFile(linkedfile); + }); } } - public void moveFilesToFileDirRenameAndAddToEntry(BibEntry entry, List files, LuceneManager luceneManager) { + public void moveFilesToFileDirRenameAndAddToEntry( + BibEntry entry, List files, LuceneManager luceneManager) { try (AutoCloseable blocker = luceneManager.blockLinkedFileIndexer()) { addFilesToEntry(entry, files); moveLinkedFilesToFileDir(entry); @@ -90,11 +102,15 @@ public void moveFilesToFileDirRenameAndAddToEntry(BibEntry entry, List fil luceneManager.updateAfterDropFiles(entry); } - public void copyFilesToFileDirAndAddToEntry(BibEntry entry, List files, LuceneManager luceneManager) { + public void copyFilesToFileDirAndAddToEntry( + BibEntry entry, List files, LuceneManager luceneManager) { try (AutoCloseable blocker = luceneManager.blockLinkedFileIndexer()) { for (Path file : files) { copyFileToFileDir(file) - .ifPresent(copiedFile -> addFilesToEntry(entry, Collections.singletonList(copiedFile))); + .ifPresent( + copiedFile -> + addFilesToEntry( + entry, Collections.singletonList(copiedFile))); } renameLinkedFilesToPattern(entry); } catch (Exception e) { @@ -108,11 +124,17 @@ private List getValidFileNames(List filesToAdd) { for (Path fileToAdd : filesToAdd) { 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), - Localization.lang("Rename and add")); + 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), + Localization.lang("Rename and add")); if (correctButtonPressed) { Path correctPath = fileToAdd.resolveSibling(newFilename); diff --git a/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java b/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java index 28a4f284c9e6..3a600c2960a7 100644 --- a/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java +++ b/src/main/java/org/jabref/gui/externalfiles/FileExtensionViewModel.java @@ -1,10 +1,5 @@ package org.jabref.gui.externalfiles; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.Path; -import java.util.List; -import java.util.stream.Collectors; - import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.frame.ExternalApplicationsPreferences; @@ -13,6 +8,11 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.FileType; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + public class FileExtensionViewModel { private final String name; @@ -20,7 +20,8 @@ public class FileExtensionViewModel { private final List extensions; private final ExternalApplicationsPreferences externalApplicationsPreferences; - FileExtensionViewModel(FileType fileType, ExternalApplicationsPreferences externalApplicationsPreferences) { + FileExtensionViewModel( + FileType fileType, ExternalApplicationsPreferences externalApplicationsPreferences) { this.name = fileType.getName(); this.description = Localization.lang("%0 file", fileType.getName()); this.extensions = fileType.getExtensionsWithAsteriskAndDot(); @@ -36,9 +37,10 @@ public String getDescription() { } public JabRefIcon getIcon() { - return ExternalFileTypes.getExternalFileTypeByExt(extensions.getFirst(), externalApplicationsPreferences) - .map(ExternalFileType::getIcon) - .orElse(null); + return ExternalFileTypes.getExternalFileTypeByExt( + extensions.getFirst(), externalApplicationsPreferences) + .map(ExternalFileType::getIcon) + .orElse(null); } public Filter dirFilter() { diff --git a/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java b/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java index 22f2aee57f58..f11fcba57095 100644 --- a/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java +++ b/src/main/java/org/jabref/gui/externalfiles/FileFilterUtils.java @@ -1,5 +1,10 @@ package org.jabref.gui.externalfiles; +import org.jabref.logic.externalfiles.DateRange; +import org.jabref.logic.externalfiles.ExternalFileSorter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -10,12 +15,6 @@ import java.util.List; import java.util.stream.Collectors; -import org.jabref.logic.externalfiles.DateRange; -import org.jabref.logic.externalfiles.ExternalFileSorter; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class FileFilterUtils { private static final Logger LOGGER = LoggerFactory.getLogger(FileFilterUtils.class); @@ -29,10 +28,8 @@ public static LocalDateTime getFileTime(Path path) { 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; } @@ -68,13 +65,14 @@ public boolean isDuringLastYear(LocalDateTime fileEditTime) { public static boolean filterByDate(Path path, DateRange filter) { FileFilterUtils fileFilter = new FileFilterUtils(); LocalDateTime fileTime = FileFilterUtils.getFileTime(path); - boolean isInDateRange = switch (filter) { - case DAY -> fileFilter.isDuringLastDay(fileTime); - case WEEK -> fileFilter.isDuringLastWeek(fileTime); - case MONTH -> fileFilter.isDuringLastMonth(fileTime); - case YEAR -> fileFilter.isDuringLastYear(fileTime); - case ALL_TIME -> true; - }; + boolean isInDateRange = + switch (filter) { + case DAY -> fileFilter.isDuringLastDay(fileTime); + case WEEK -> fileFilter.isDuringLastWeek(fileTime); + case MONTH -> fileFilter.isDuringLastMonth(fileTime); + case YEAR -> fileFilter.isDuringLastYear(fileTime); + case ALL_TIME -> true; + }; return isInDateRange; } @@ -84,10 +82,13 @@ public static boolean filterByDate(Path path, DateRange filter) { */ public List sortByDateAscending(List files) { return files.stream() - .sorted(Comparator.comparingLong(file -> FileFilterUtils.getFileTime(file) - .atZone(ZoneId.systemDefault()) - .toInstant() - .toEpochMilli())) + .sorted( + Comparator.comparingLong( + file -> + FileFilterUtils.getFileTime(file) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli())) .collect(Collectors.toList()); } @@ -97,10 +98,13 @@ public List sortByDateAscending(List files) { */ public List sortByDateDescending(List files) { return files.stream() - .sorted(Comparator.comparingLong(file -> -FileFilterUtils.getFileTime(file) - .atZone(ZoneId.systemDefault()) - .toInstant() - .toEpochMilli())) + .sorted( + Comparator.comparingLong( + file -> + -FileFilterUtils.getFileTime(file) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli())) .collect(Collectors.toList()); } @@ -110,12 +114,12 @@ public List sortByDateDescending(List files) { */ public static List sortByDate(List files, ExternalFileSorter sortType) { FileFilterUtils fileFilter = new FileFilterUtils(); - List sortedFiles = switch (sortType) { - case DEFAULT -> files; - case DATE_ASCENDING -> fileFilter.sortByDateDescending(files); - case DATE_DESCENDING -> fileFilter.sortByDateAscending(files); - }; + List sortedFiles = + switch (sortType) { + case DEFAULT -> files; + case DATE_ASCENDING -> fileFilter.sortByDateDescending(files); + case DATE_DESCENDING -> fileFilter.sortByDateAscending(files); + }; return sortedFiles; } } - diff --git a/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java b/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java index 7c5cb7bf6447..60eadc8f0040 100644 --- a/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesAction.java @@ -1,11 +1,11 @@ package org.jabref.gui.externalfiles; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; - public class FindUnlinkedFilesAction extends SimpleCommand { private final DialogService dialogService; diff --git a/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java b/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java index e396c4f883a9..b3cda0fadca5 100644 --- a/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java +++ b/src/main/java/org/jabref/gui/externalfiles/GitIgnoreFileFilter.java @@ -1,5 +1,10 @@ package org.jabref.gui.externalfiles; +import static java.util.function.Predicate.not; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.FileSystems; @@ -10,11 +15,6 @@ import java.util.Set; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static java.util.function.Predicate.not; - public class GitIgnoreFileFilter implements DirectoryStream.Filter { private static final Logger LOGGER = LoggerFactory.getLogger(GitIgnoreFileFilter.class); @@ -28,22 +28,29 @@ 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()); + 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")); @@ -57,10 +64,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 - return gitIgnorePatterns.stream().noneMatch(filter -> - // we need this one for "*.png" - filter.matches(path.getFileName()) || - // we need this one for "**/*.png" - filter.matches(path)); + return gitIgnorePatterns.stream() + .noneMatch( + filter -> + // we need this one for "*.png" + filter.matches(path.getFileName()) + || + // we need this one for "**/*.png" + filter.matches(path)); } } diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java b/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java index 3677107b4278..8e3f5735fb36 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportFilesResultItemViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.externalfiles; -import java.nio.file.Path; - import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -11,10 +9,13 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; +import java.nio.file.Path; + public class ImportFilesResultItemViewModel { private final StringProperty file = new SimpleStringProperty(""); - private final ObjectProperty icon = new SimpleObjectProperty<>(IconTheme.JabRefIcons.WARNING); + private final ObjectProperty icon = + new SimpleObjectProperty<>(IconTheme.JabRefIcons.WARNING); private final StringProperty message = new SimpleStringProperty(""); public ImportFilesResultItemViewModel(Path file, boolean success, String message) { @@ -41,6 +42,10 @@ public StringProperty message() { @Override public String toString() { - return "ImportFilesResultItemViewModel [file=" + file.get() + ", message=" + message.get() + "]"; + return "ImportFilesResultItemViewModel [file=" + + file.get() + + ", message=" + + message.get() + + "]"; } } diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index 669aa9a21bc4..c0cf8856e561 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -1,17 +1,9 @@ package org.jabref.gui.externalfiles; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import static org.jabref.gui.duplicationFinder.DuplicateResolverDialog.DuplicateResolverResult.BREAK; -import javax.swing.undo.CompoundEdit; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; +import com.google.common.annotations.VisibleForTesting; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -55,13 +47,21 @@ import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.model.util.OptionalUtil; - -import com.airhacks.afterburner.injection.Injector; -import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.duplicationFinder.DuplicateResolverDialog.DuplicateResolverResult.BREAK; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoManager; public class ImportHandler { @@ -76,13 +76,14 @@ public class ImportHandler { 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; @@ -91,8 +92,14 @@ public ImportHandler(BibDatabaseContext database, this.dialogService = dialogService; this.taskExecutor = taskExecutor; - this.linker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), database, dialogService); - this.contentImporter = new ExternalFilesContentImporter(preferences.getImportFormatPreferences()); + this.linker = + new ExternalFilesEntryLinker( + preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), + database, + dialogService); + this.contentImporter = + new ExternalFilesContentImporter(preferences.getImportFormatPreferences()); this.undoManager = undoManager; } @@ -100,7 +107,10 @@ public ExternalFilesEntryLinker getLinker() { return linker; } - public BackgroundTask> importFilesInBackground(final List files, final BibDatabaseContext bibDatabaseContext, final FilePreferences filePreferences) { + public BackgroundTask> importFilesInBackground( + final List files, + final BibDatabaseContext bibDatabaseContext, + final FilePreferences filePreferences) { return new BackgroundTask<>() { private int counter; private final List results = new ArrayList<>(); @@ -116,50 +126,88 @@ public List call() { break; } - UiTaskExecutor.runInJavaFXThread(() -> { - updateMessage(Localization.lang("Processing file %0", file.getFileName())); - updateProgress(counter, files.size() - 1d); - }); + UiTaskExecutor.runInJavaFXThread( + () -> { + updateMessage( + Localization.lang( + "Processing file %0", file.getFileName())); + updateProgress(counter, files.size() - 1d); + }); try { if (FileUtil.isPDFFile(file)) { var pdfImporterResult = contentImporter.importPDFContent(file); - List pdfEntriesInFile = pdfImporterResult.getDatabase().getEntries(); + List 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.addAll(FileUtil.relativize(pdfEntriesInFile, bibDatabaseContext, filePreferences)); - addResultToList(file, true, Localization.lang("File was successfully imported as a new entry")); + entriesToAdd.addAll( + FileUtil.relativize( + pdfEntriesInFile, + bibDatabaseContext, + filePreferences)); + addResultToList( + file, + true, + Localization.lang( + "File was successfully imported as a new entry")); } else { entriesToAdd.add(createEmptyEntryWithLink(file)); - addResultToList(file, false, Localization.lang("No BibTeX was found. An empty entry was created with file link.")); + addResultToList( + file, + false, + Localization.lang( + "No BibTeX was found. An empty entry was created with file link.")); } } else if (FileUtil.isBibFile(file)) { - var bibtexParserResult = contentImporter.importFromBibFile(file, fileUpdateMonitor); + var bibtexParserResult = + contentImporter.importFromBibFile(file, fileUpdateMonitor); if (bibtexParserResult.hasWarnings()) { addResultToList(file, false, bibtexParserResult.getErrorMessage()); } - entriesToAdd.addAll(bibtexParserResult.getDatabaseContext().getEntries()); - addResultToList(file, true, Localization.lang("Bib entry was successfully imported")); + entriesToAdd.addAll( + bibtexParserResult.getDatabaseContext().getEntries()); + addResultToList( + file, + true, + Localization.lang("Bib entry was successfully imported")); } else { entriesToAdd.add(createEmptyEntryWithLink(file)); - 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) { LOGGER.error("Error importing", ex); - addResultToList(file, false, Localization.lang("Error from import: %0", ex.getLocalizedMessage())); - - UiTaskExecutor.runInJavaFXThread(() -> updateMessage(Localization.lang("Error"))); + addResultToList( + file, + false, + Localization.lang( + "Error from import: %0", ex.getLocalizedMessage())); + + UiTaskExecutor.runInJavaFXThread( + () -> updateMessage(Localization.lang("Error"))); } - // 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 UiTaskExecutor.runInJavaFXThread(() -> importEntries(entriesToAdd)); - ce.addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entriesToAdd)); + ce.addEdit( + new UndoableInsertEntries( + bibDatabaseContext.getDatabase(), entriesToAdd)); ce.end(); // prevent fx thread exception in undo manager UiTaskExecutor.runInJavaFXThread(() -> undoManager.addEdit(ce)); @@ -188,7 +236,9 @@ private BibEntry createEmptyEntryWithLink(Path file) { * 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); } @@ -200,42 +250,64 @@ public void importCleanedEntries(List entries) { addToGroups(entries, stateManager.getSelectedGroups(bibDatabaseContext)); } - public void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, BibEntry entry) { + public void importEntryWithDuplicateCheck( + BibDatabaseContext bibDatabaseContext, BibEntry entry) { importEntryWithDuplicateCheck(bibDatabaseContext, entry, BREAK); } - private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, BibEntry entry, DuplicateResolverDialog.DuplicateResolverResult decision) { + private void importEntryWithDuplicateCheck( + BibDatabaseContext bibDatabaseContext, + BibEntry entry, + DuplicateResolverDialog.DuplicateResolverResult decision) { BibEntry entryToInsert = cleanUpEntry(bibDatabaseContext, entry); BackgroundTask.wrap(() -> findDuplicate(bibDatabaseContext, entryToInsert)) - .onFailure(e -> LOGGER.error("Error in duplicate search")) - .onSuccess(existingDuplicateInLibrary -> { - BibEntry finalEntry = entryToInsert; - if (existingDuplicateInLibrary.isPresent()) { - Optional duplicateHandledEntry = handleDuplicates(bibDatabaseContext, entryToInsert, existingDuplicateInLibrary.get(), decision); - if (duplicateHandledEntry.isEmpty()) { - return; - } - finalEntry = duplicateHandledEntry.get(); - } - importCleanedEntries(List.of(finalEntry)); - downloadLinkedFiles(finalEntry); - }).executeWith(taskExecutor); + .onFailure(e -> LOGGER.error("Error in duplicate search")) + .onSuccess( + existingDuplicateInLibrary -> { + BibEntry finalEntry = entryToInsert; + if (existingDuplicateInLibrary.isPresent()) { + Optional duplicateHandledEntry = + handleDuplicates( + bibDatabaseContext, + entryToInsert, + existingDuplicateInLibrary.get(), + decision); + if (duplicateHandledEntry.isEmpty()) { + return; + } + finalEntry = duplicateHandledEntry.get(); + } + importCleanedEntries(List.of(finalEntry)); + downloadLinkedFiles(finalEntry); + }) + .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) { + 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) { - DuplicateDecisionResult decisionResult = getDuplicateDecision(originalEntry, duplicateEntry, 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: bibDatabaseContext.getDatabase().removeEntry(duplicateEntry); @@ -254,39 +326,48 @@ 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); } if (preferences.getMergeDialogPreferences().shouldMergeApplyToAllEntries()) { - preferences.getMergeDialogPreferences().setAllEntriesDuplicateResolverDecision(decision); + preferences + .getMergeDialogPreferences() + .setAllEntriesDuplicateResolverDecision(decision); } return new DuplicateDecisionResult(decision, dialog.getMergedEntry()); } public void setAutomaticFields(List entries) { UpdateField.setAutomaticFields( - entries, - preferences.getOwnerPreferences(), - preferences.getTimestampPreferences() - ); + 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) - ); + .filter(LinkedFile::isOnlineLink) + .forEach( + linkedFile -> + new LinkedFileViewModel( + linkedFile, + entry, + bibDatabaseContext, + taskExecutor, + dialogService, + preferences) + .download(false)); } } @@ -296,7 +377,8 @@ 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), + // ce.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(new + // GroupTreeNodeViewModel(node), // undo)); // } } @@ -312,18 +394,26 @@ private void generateKeys(List entries) { if (!preferences.getImporterPreferences().isGenerateNewKeyOnImport()) { return; } - CitationKeyGenerator keyGenerator = new CitationKeyGenerator( - bibDatabaseContext.getMetaData().getCiteKeyPatterns(preferences.getCitationKeyPatternPreferences() - .getKeyPatterns()), - bibDatabaseContext.getDatabase(), - preferences.getCitationKeyPatternPreferences()); + CitationKeyGenerator keyGenerator = + new CitationKeyGenerator( + 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); + 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; @@ -338,18 +428,30 @@ 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())); + 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())); + 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))); } } @@ -378,12 +480,12 @@ 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 @@ -394,33 +496,44 @@ private List tryImportFormats(String data) { private List fetchByDOI(DOI doi) throws FetcherException { LOGGER.info("Found DOI identifier in clipboard"); - Optional entry = new DoiFetcher(preferences.getImportFormatPreferences()).performSearchById(doi.getDOI()); + Optional entry = + new DoiFetcher(preferences.getImportFormatPreferences()) + .performSearchById(doi.getDOI()); return OptionalUtil.toList(entry); } private List fetchByArXiv(ArXivIdentifier arXivIdentifier) throws FetcherException { LOGGER.info("Found arxiv identifier in clipboard"); - Optional entry = new ArXivFetcher(preferences.getImportFormatPreferences()).performSearchById(arXivIdentifier.getNormalizedWithoutVersion()); + Optional entry = + new ArXivFetcher(preferences.getImportFormatPreferences()) + .performSearchById(arXivIdentifier.getNormalizedWithoutVersion()); return OptionalUtil.toList(entry); } private List fetchByISBN(ISBN isbn) throws FetcherException { LOGGER.info("Found ISBN identifier in clipboard"); - Optional entry = new IsbnFetcher(preferences.getImportFormatPreferences()).performSearchById(isbn.getNormalized()); + Optional entry = + new IsbnFetcher(preferences.getImportFormatPreferences()) + .performSearchById(isbn.getNormalized()); return OptionalUtil.toList(entry); } - public void importEntriesWithDuplicateCheck(BibDatabaseContext database, List entriesToAdd) { + public void importEntriesWithDuplicateCheck( + BibDatabaseContext database, List entriesToAdd) { boolean firstEntry = true; for (BibEntry entry : entriesToAdd) { if (firstEntry) { - LOGGER.debug("First entry to import, we use BREAK (\"Ask every time\") as decision"); + LOGGER.debug( + "First entry to import, we use BREAK (\"Ask every time\") as decision"); importEntryWithDuplicateCheck(database, entry, BREAK); firstEntry = false; continue; } if (preferences.getMergeDialogPreferences().shouldMergeApplyToAllEntries()) { - DuplicateResolverDialog.DuplicateResolverResult decision = preferences.getMergeDialogPreferences().getAllEntriesDuplicateResolverDecision(); + DuplicateResolverDialog.DuplicateResolverResult decision = + preferences + .getMergeDialogPreferences() + .getAllEntriesDuplicateResolverDecision(); LOGGER.debug("Not first entry, pref flag is true, we use {}", decision); importEntryWithDuplicateCheck(database, entry, decision); } else { diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java index 93986d5743e3..cf3a684b2e4e 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesCrawler.java @@ -1,17 +1,5 @@ package org.jabref.gui.externalfiles; -import java.io.File; -import java.io.IOException; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - import javafx.scene.control.CheckBoxTreeItem; import org.jabref.gui.util.FileNodeViewModel; @@ -21,10 +9,21 @@ import org.jabref.logic.util.BackgroundTask; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + /** * Util class for searching files on the file system which are not linked to a provided {@link BibDatabase}. */ @@ -39,7 +38,13 @@ public class UnlinkedFilesCrawler extends BackgroundTask { 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; @@ -50,7 +55,8 @@ 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); } @@ -76,7 +82,8 @@ public FileNodeViewModel call() throws IOException { * @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 { + FileNodeViewModel searchDirectory(Path directory, UnlinkedPDFFileFilter unlinkedPDFFileFilter) + throws IOException { // Return null if the directory is not valid. if ((directory == null) || !Files.isDirectory(directory)) { throw new IOException("Invalid directory for searching: %s".formatted(directory)); @@ -85,14 +92,19 @@ 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 - ChainedFilters filters = new ChainedFilters(unlinkedPDFFileFilter, new GitIgnoreFileFilter(directory)); + ChainedFilters filters = + new ChainedFilters(unlinkedPDFFileFilter, new GitIgnoreFileFilter(directory)); Map> directoryAndFilePartition; - try (Stream filesStream = StreamSupport.stream(Files.newDirectoryStream(directory, filters).spliterator(), false)) { - directoryAndFilePartition = filesStream.collect(Collectors.partitioningBy(Files::isDirectory)); + try (Stream filesStream = + StreamSupport.stream( + Files.newDirectoryStream(directory, filters).spliterator(), false)) { + directoryAndFilePartition = + filesStream.collect(Collectors.partitioningBy(Files::isDirectory)); } catch (IOException e) { LOGGER.error("Error while searching files", e); return fileNodeViewModelForCurrentDirectory; @@ -119,7 +131,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)) { @@ -130,13 +143,18 @@ 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 - fileNodeViewModelForCurrentDirectory.setFileCount(resultingFiles.size() + fileCountOfSubdirectories); + // 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) - .collect(Collectors.toList())); + fileNodeViewModelForCurrentDirectory + .getChildren() + .addAll( + resultingFiles.stream() + .map(FileNodeViewModel::new) + .collect(Collectors.toList())); return fileNodeViewModelForCurrentDirectory; } diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java index 26324a7e9491..ad2cfb5c497b 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogPreferences.java @@ -13,11 +13,14 @@ public class UnlinkedFilesDialogPreferences { private final ObjectProperty unlinkedFilesSelectedDateRange; private final ObjectProperty unlinkedFilesSelectedSort; - public UnlinkedFilesDialogPreferences(String unlinkedFilesSelectedExtension, - DateRange unlinkedFilesSelectedDateRange, - ExternalFileSorter unlinkedFilesSelectedSort) { - this.unlinkedFilesSelectedExtension = new SimpleStringProperty(unlinkedFilesSelectedExtension); - this.unlinkedFilesSelectedDateRange = new SimpleObjectProperty<>(unlinkedFilesSelectedDateRange); + public UnlinkedFilesDialogPreferences( + String unlinkedFilesSelectedExtension, + DateRange unlinkedFilesSelectedDateRange, + ExternalFileSorter unlinkedFilesSelectedSort) { + this.unlinkedFilesSelectedExtension = + new SimpleStringProperty(unlinkedFilesSelectedExtension); + this.unlinkedFilesSelectedDateRange = + new SimpleObjectProperty<>(unlinkedFilesSelectedDateRange); this.unlinkedFilesSelectedSort = new SimpleObjectProperty<>(unlinkedFilesSelectedSort); } diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java index b148818ebb55..e7d38a9f72bb 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogView.java @@ -1,9 +1,11 @@ package org.jabref.gui.externalfiles; -import java.nio.file.Path; -import java.util.Objects; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; -import javax.swing.undo.UndoManager; +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -28,6 +30,7 @@ import javafx.scene.control.TreeItem; import javafx.scene.layout.VBox; +import org.controlsfx.control.CheckTreeView; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; @@ -51,11 +54,10 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; -import org.controlsfx.control.CheckTreeView; +import java.nio.file.Path; +import java.util.Objects; + +import javax.swing.undo.UndoManager; public class UnlinkedFilesDialogView extends BaseDialog { @@ -98,32 +100,35 @@ 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) { - viewModel.cancelTasks(); - } - saveConfiguration(); - return null; - }); + setResultConverter( + button -> { + if (button == ButtonType.CANCEL) { + viewModel.cancelTasks(); + } + saveConfiguration(); + return null; + }); themeManager.updateFontStyle(getDialogPane().getScene()); } @FXML private void initialize() { - viewModel = new UnlinkedFilesDialogViewModel( - dialogService, - undoManager, - fileUpdateMonitor, - preferences, - stateManager, - taskExecutor); - - this.bibDatabaseContext = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("No active library")); + viewModel = + new UnlinkedFilesDialogViewModel( + dialogService, + undoManager, + fileUpdateMonitor, + preferences, + stateManager, + taskExecutor); + + this.bibDatabaseContext = + stateManager + .getActiveDatabase() + .orElseThrow(() -> new NullPointerException("No active library")); progressDisplay.progressProperty().bind(viewModel.progressValueProperty()); progressText.textProperty().bind(viewModel.progressTextProperty()); @@ -131,19 +136,26 @@ private void initialize() { progressPane.visibleProperty().bind(viewModel.taskActiveProperty()); accordion.disableProperty().bind(viewModel.taskActiveProperty()); - viewModel.treeRootProperty().addListener(observable -> { - scanButton.setDefaultButton(false); - importButton.setDefaultButton(true); - scanButton.setDefaultButton(false); - filePane.setExpanded(true); - resultPane.setExpanded(false); - }); - - viewModel.resultTableItems().addListener((InvalidationListener) observable -> { - filePane.setExpanded(false); - resultPane.setExpanded(true); - resultPane.setDisable(false); - }); + viewModel + .treeRootProperty() + .addListener( + observable -> { + scanButton.setDefaultButton(false); + importButton.setDefaultButton(true); + scanButton.setDefaultButton(false); + filePane.setExpanded(true); + resultPane.setExpanded(false); + }); + + viewModel + .resultTableItems() + .addListener( + (InvalidationListener) + observable -> { + filePane.setExpanded(false); + resultPane.setExpanded(true); + resultPane.setDisable(false); + }); initDirectorySelection(); initUnlinkedFilesList(); @@ -155,7 +167,10 @@ 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) @@ -165,8 +180,8 @@ private void initDirectorySelection() { fileTypeCombo.valueProperty().bindBidirectional(viewModel.selectedExtensionProperty()); new ViewModelListCellFactory() - .withText(DateRange::getDateRange) - .install(fileDateCombo); + .withText(DateRange::getDateRange) + .install(fileDateCombo); fileDateCombo.setItems(viewModel.getDateFilters()); fileDateCombo.valueProperty().bindBidirectional(viewModel.selectedDateProperty()); @@ -176,7 +191,11 @@ private void initDirectorySelection() { 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(); } @@ -185,70 +204,122 @@ private void initUnlinkedFilesList() { .withText(FileNodeViewModel::getDisplayTextWithEditDate) .install(unlinkedFilesList); - unlinkedFilesList.maxHeightProperty().bind(((Control) filePane.contentProperty().get()).heightProperty()); + 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()); - EasyBind.subscribe(unlinkedFilesList.rootProperty(), root -> { - if (root != null) { - ((CheckBoxTreeItem) root).setSelected(true); - root.setExpanded(true); - EasyBind.bindContent(viewModel.checkedFileListProperty(), unlinkedFilesList.getCheckModel().getCheckedItems()); - } else { - EasyBind.bindContent(viewModel.checkedFileListProperty(), FXCollections.observableArrayList()); - } - }); + EasyBind.subscribe( + unlinkedFilesList.rootProperty(), + root -> { + if (root != null) { + ((CheckBoxTreeItem) root).setSelected(true); + root.setExpanded(true); + EasyBind.bindContent( + viewModel.checkedFileListProperty(), + unlinkedFilesList.getCheckModel().getCheckedItems()); + } else { + EasyBind.bindContent( + viewModel.checkedFileListProperty(), + FXCollections.observableArrayList()); + } + }); } private void initResultTable() { colFile.setCellValueFactory(cellData -> cellData.getValue().file()); new ValueTableCellFactory() - .withText(item -> item).withTooltip(item -> item) + .withText(item -> item) + .withTooltip(item -> item) .install(colFile); colMessage.setCellValueFactory(cellData -> cellData.getValue().message()); new ValueTableCellFactory() - .withText(item -> item).withTooltip(item -> item) + .withText(item -> item) + .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)); importResultTable.setColumnResizePolicy(param -> true); importResultTable.setItems(viewModel.resultTableItems()); } private void initButtons() { - BooleanBinding noItemsChecked = Bindings.isNull(unlinkedFilesList.rootProperty()) - .or(Bindings.isEmpty(viewModel.checkedFileListProperty())); + BooleanBinding noItemsChecked = + Bindings.isNull(unlinkedFilesList.rootProperty()) + .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())); + 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())); fileTypeCombo.getSelectionModel().select(selectedExtension); - fileDateCombo.getSelectionModel().select(unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedDateRange()); - fileSortCombo.getSelectionModel().select(unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedSort()); + fileDateCombo + .getSelectionModel() + .select(unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedDateRange()); + fileSortCombo + .getSelectionModel() + .select(unlinkedFilesDialogPreferences.getUnlinkedFilesSelectedSort()); } public void saveConfiguration() { - preferences.getUnlinkedFilesDialogPreferences().setUnlinkedFilesSelectedExtension(fileTypeCombo.getValue().getName()); - preferences.getUnlinkedFilesDialogPreferences().setUnlinkedFilesSelectedDateRange(fileDateCombo.getValue()); - preferences.getUnlinkedFilesDialogPreferences().setUnlinkedFilesSelectedSort(fileSortCombo.getValue()); + preferences + .getUnlinkedFilesDialogPreferences() + .setUnlinkedFilesSelectedExtension(fileTypeCombo.getValue().getName()); + preferences + .getUnlinkedFilesDialogPreferences() + .setUnlinkedFilesSelectedDateRange(fileDateCombo.getValue()); + preferences + .getUnlinkedFilesDialogPreferences() + .setUnlinkedFilesSelectedSort(fileSortCombo.getValue()); } @FXML @@ -287,10 +358,30 @@ 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; } diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java index ecee35887f35..c8fad5601913 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedFilesDialogViewModel.java @@ -1,18 +1,8 @@ package org.jabref.gui.externalfiles; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; @@ -43,31 +33,46 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; - -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + public class UnlinkedFilesDialogViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(UnlinkedFilesDialogViewModel.class); + 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 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 ObjectProperty> treeRootProperty = + new SimpleObjectProperty<>(); + 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 resultList = + FXCollections.observableArrayList(); private final ObservableList fileFilterList; private final ObservableList dateFilterList; private final ObservableList fileSortList; @@ -82,38 +87,56 @@ public class UnlinkedFilesDialogViewModel { 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.fileFilterList = FXCollections.observableArrayList( - new FileExtensionViewModel(StandardFileType.ANY_FILE, preferences.getExternalApplicationsPreferences()), - new FileExtensionViewModel(StandardFileType.HTML, preferences.getExternalApplicationsPreferences()), - new FileExtensionViewModel(StandardFileType.MARKDOWN, preferences.getExternalApplicationsPreferences()), - new FileExtensionViewModel(StandardFileType.PDF, preferences.getExternalApplicationsPreferences())); + 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()), + new FileExtensionViewModel( + StandardFileType.HTML, + preferences.getExternalApplicationsPreferences()), + new FileExtensionViewModel( + StandardFileType.MARKDOWN, + preferences.getExternalApplicationsPreferences()), + new FileExtensionViewModel( + StandardFileType.PDF, + preferences.getExternalApplicationsPreferences())); this.dateFilterList = FXCollections.observableArrayList(DateRange.values()); this.fileSortList = FXCollections.observableArrayList(ExternalFileSorter.values()); Predicate isDirectory = path -> Files.isDirectory(Path.of(path)); - scanDirectoryValidator = new FunctionBasedValidator<>(directoryPath, isDirectory, - ValidationMessage.error(Localization.lang("Please enter a valid file path."))); + scanDirectoryValidator = + new FunctionBasedValidator<>( + directoryPath, + isDirectory, + ValidationMessage.error( + Localization.lang("Please enter a valid file path."))); treeRootProperty.setValue(Optional.empty()); } @@ -126,46 +149,66 @@ 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(item -> item.getValue().getPath()) - .filter(path -> path.toFile().isFile()) - .collect(Collectors.toList()); + List fileList = + checkedFileListProperty.stream() + .map(item -> item.getValue().getPath()) + .filter(path -> path.toFile().isFile()) + .collect(Collectors.toList()); if (fileList.isEmpty()) { LOGGER.warn("There are no valid files checked"); return; } resultList.clear(); - importFilesBackgroundTask = importHandler.importFilesInBackground(fileList, bibDatabase, preferences.getFilePreferences()) - .onRunning(() -> { - progressValueProperty.bind(importFilesBackgroundTask.workDonePercentageProperty()); - progressTextProperty.bind(importFilesBackgroundTask.messageProperty()); - taskActiveProperty.setValue(true); - }) - .onFinished(() -> { - progressValueProperty.unbind(); - progressTextProperty.unbind(); - taskActiveProperty.setValue(false); - }) - .onSuccess(resultList::addAll); + importFilesBackgroundTask = + importHandler + .importFilesInBackground( + fileList, bibDatabase, preferences.getFilePreferences()) + .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); } @@ -173,35 +216,39 @@ 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(path -> path.toFile().isFile()) - .toList(); + List fileList = + checkedFileListProperty.stream() + .map(item -> item.getValue().getPath()) + .filter(path -> path.toFile().isFile()) + .toList(); if (fileList.isEmpty()) { LOGGER.warn("There are no valid files checked"); return; } - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()) - .addExtensionFilter(StandardFileType.TXT) - .withDefaultExtension(StandardFileType.TXT) - .build(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .withInitialDirectory( + preferences.getFilePreferences().getWorkingDirectory()) + .addExtensionFilter(StandardFileType.TXT) + .withDefaultExtension(StandardFileType.TXT) + .build(); Optional exportPath = dialogService.showFileSaveDialog(fileDialogConfiguration); if (exportPath.isEmpty()) { return; } - try (BufferedWriter writer = Files.newBufferedWriter(exportPath.get(), StandardCharsets.UTF_8, - StandardOpenOption.CREATE)) { + try (BufferedWriter writer = + Files.newBufferedWriter( + exportPath.get(), StandardCharsets.UTF_8, StandardOpenOption.CREATE)) { for (Path file : fileList) { writer.write(file.toString() + "\n"); } } catch (IOException e) { LOGGER.error("Error exporting", e); } - } + } public ObservableList getFileFilters() { return this.fileFilterList; @@ -225,14 +272,21 @@ public void cancelTasks() { } public void browseFileDirectory() { - DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()).build(); - - dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(selectedDirectory -> { - directoryPath.setValue(selectedDirectory.toAbsolutePath().toString()); - preferences.getFilePreferences().setWorkingDirectory(selectedDirectory.toAbsolutePath()); - }); + DirectoryDialogConfiguration directoryDialogConfiguration = + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory( + preferences.getFilePreferences().getWorkingDirectory()) + .build(); + + dialogService + .showDirectorySelectionDialog(directoryDialogConfiguration) + .ifPresent( + selectedDirectory -> { + directoryPath.setValue(selectedDirectory.toAbsolutePath().toString()); + preferences + .getFilePreferences() + .setWorkingDirectory(selectedDirectory.toAbsolutePath()); + }); } private Path getSearchDirectory() { diff --git a/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java b/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java index 6b684596dd77..97ab3587da8b 100644 --- a/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java +++ b/src/main/java/org/jabref/gui/externalfiles/UnlinkedPDFFileFilter.java @@ -1,5 +1,11 @@ package org.jabref.gui.externalfiles; +import org.jabref.logic.FilePreferences; +import org.jabref.logic.util.io.DatabaseFileLookup; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; + import java.io.FileFilter; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -7,12 +13,6 @@ import java.nio.file.Files; import java.nio.file.Path; -import org.jabref.logic.FilePreferences; -import org.jabref.logic.util.io.DatabaseFileLookup; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -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}. @@ -25,7 +25,10 @@ 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); } @@ -35,7 +38,9 @@ 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); + return fileFilter.accept(pathname) + && !lookup.lookupDatabase(pathname) + && !lookup.getPathOfDatabase().equals(pathname); } } } diff --git a/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java b/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java index 396e1cacf979..9f50e01b0913 100644 --- a/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java +++ b/src/main/java/org/jabref/gui/externalfiletype/CustomExternalFileType.java @@ -1,11 +1,11 @@ package org.jabref.gui.externalfiletype; -import java.util.Objects; - import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; import org.jabref.logic.FilePreferences; +import java.util.Objects; + /** * 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 @@ -22,8 +22,13 @@ public class CustomExternalFileType implements ExternalFileType { 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,7 +39,13 @@ 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()); } /** @@ -47,7 +58,8 @@ public CustomExternalFileType(ExternalFileType type) { */ 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]; @@ -182,8 +194,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/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java index 0a5a44259cfb..95866b53177e 100644 --- a/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java +++ b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileType.java @@ -32,6 +32,8 @@ default Field getField() { * @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/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java index 4a79e57b45c8..7b412d6836c7 100644 --- a/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java +++ b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java @@ -1,5 +1,11 @@ package org.jabref.gui.externalfiletype; +import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.logic.bibtex.FileFieldWriter; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.strings.StringUtil; + import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -10,12 +16,6 @@ import java.util.Optional; import java.util.Set; -import org.jabref.gui.frame.ExternalApplicationsPreferences; -import org.jabref.logic.bibtex.FileFieldWriter; -import org.jabref.logic.util.io.FileUtil; -import org.jabref.model.entry.LinkedFile; -import org.jabref.model.strings.StringUtil; - // Do not make this class final, as it otherwise can't be mocked for tests public class ExternalFileTypes { @@ -24,8 +24,7 @@ public class ExternalFileTypes { private static final String FILE_TYPE_REMOVED_FLAG = "REMOVED"; private static final ExternalFileType HTML_FALLBACK_TYPE = StandardExternalFileType.URL; - private ExternalFileTypes() { - } + private ExternalFileTypes() {} public static List getDefaultExternalFileTypes() { return Arrays.asList(StandardExternalFileType.values()); @@ -37,8 +36,12 @@ public static List getDefaultExternalFileTypes() { * @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; } @@ -52,9 +55,12 @@ public static Optional getExternalFileTypeByName(String name, * @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(); } /** @@ -63,8 +69,10 @@ public static Optional getExternalFileTypeByExt(String extensi * @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)); } /** @@ -73,11 +81,14 @@ public static boolean isExternalFileTypeByExt(String extension, ExternalApplicat * @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; @@ -93,8 +104,10 @@ public static Optional getExternalFileTypeForName(String filen * @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(); } @@ -110,26 +123,38 @@ public static Optional getExternalFileTypeByMimeType(String mi } } - 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)); + 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); + 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)); + .flatMap( + extension -> + getExternalFileTypeByExt( + extension, externalApplicationsPreferences)); } else { return type; } diff --git a/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java b/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java index fc12e9670e8c..0ed4d68b6d70 100644 --- a/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java +++ b/src/main/java/org/jabref/gui/externalfiletype/StandardExternalFileType.java @@ -5,27 +5,116 @@ import org.jabref.logic.l10n.Localization; 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), - 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), - 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), - JPG(Localization.lang("%0 image", "JPG"), "jpg", "image/jpeg", "gimp", "picture", IconTheme.JabRefIcons.PICTURE), + 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), + 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), + 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), + JPG( + Localization.lang("%0 image", "JPG"), + "jpg", + "image/jpeg", + "gimp", + "picture", + IconTheme.JabRefIcons.PICTURE), Djvu("Djvu", "djvu", "image/vnd.djvu", "evince", "psSmall", IconTheme.JabRefIcons.FILE), TXT("Text", "txt", "text/plain", "emacs", "emacs", IconTheme.JabRefIcons.FILE_TEXT), TEX("LaTeX", "tex", "application/x-latex", "emacs", "emacs", IconTheme.JabRefIcons.FILE_TEXT), CHM("CHM", "chm", "application/mshelp", "gnochm", "www", IconTheme.JabRefIcons.WWW), - TIFF(Localization.lang("%0 image", "TIFF"), "tiff", "image/tiff", "gimp", "picture", IconTheme.JabRefIcons.PICTURE), + TIFF( + Localization.lang("%0 image", "TIFF"), + "tiff", + "image/tiff", + "gimp", + "picture", + IconTheme.JabRefIcons.PICTURE), URL("URL", "html", "text/html", "firefox", "www", IconTheme.JabRefIcons.WWW), MHT("MHT", "mht", "multipart/related", "firefox", "www", IconTheme.JabRefIcons.WWW), ePUB("ePUB", "epub", "application/epub+zip", "firefox", "www", IconTheme.JabRefIcons.WWW), @@ -37,8 +126,13 @@ public enum StandardExternalFileType implements ExternalFileType { 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 +158,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 ""; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java index 5fada27175aa..dd3a101f99b9 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/AbstractEditorViewModel.java @@ -1,12 +1,16 @@ package org.jabref.gui.fieldeditors; -import java.util.Collection; +import com.tobiasdiez.easybind.EasyObservableValue; -import javax.swing.undo.UndoManager; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import org.controlsfx.control.textfield.AutoCompletionBinding; import org.jabref.gui.AbstractViewModel; import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.gui.undo.UndoableFieldChange; @@ -16,12 +20,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.tobiasdiez.easybind.EasyObservableValue; -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.Validator; -import org.controlsfx.control.textfield.AutoCompletionBinding; +import java.util.Collection; + +import javax.swing.undo.UndoManager; public class AbstractEditorViewModel extends AbstractViewModel { protected final Field field; @@ -32,15 +33,24 @@ public class AbstractEditorViewModel extends AbstractViewModel { 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); } } @@ -64,13 +74,19 @@ public void bindToEntry(BibEntry entry) { fieldBinding, newValue -> { if (newValue != null) { - // A file may be loaded using CRLF. ControlsFX uses hardcoded \n for multiline fields. + // 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); + // 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)); + undoManager.addEdit( + new UndoableFieldChange(entry, field, oldValue, newValue)); } } }); diff --git a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java index 53251ba46a2c..fd336352608a 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditor.java @@ -1,8 +1,8 @@ package org.jabref.gui.fieldeditors; -import java.util.Collections; +import com.airhacks.afterburner.views.ViewLoader; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.Parent; @@ -22,8 +22,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.util.Collections; + +import javax.swing.undo.UndoManager; public class CitationKeyEditor extends HBox implements FieldEditorFX { @@ -36,29 +37,32 @@ public class CitationKeyEditor extends HBox implements FieldEditorFX { @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); + 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,10 +74,11 @@ 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 diff --git a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java index 109258ae0845..ce2a3b4c4328 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/CitationKeyEditorViewModel.java @@ -1,6 +1,6 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; +import de.saxsys.mvvmfx.utils.commands.Command; import org.jabref.gui.DialogService; import org.jabref.gui.autocompleter.SuggestionProvider; @@ -10,7 +10,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.field.Field; -import de.saxsys.mvvmfx.utils.commands.Command; +import javax.swing.undo.UndoManager; public class CitationKeyEditorViewModel extends AbstractEditorViewModel { private final CliPreferences preferences; @@ -18,13 +18,14 @@ public class CitationKeyEditorViewModel extends AbstractEditorViewModel { 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; @@ -33,6 +34,7 @@ public CitationKeyEditorViewModel(Field field, } public Command getGenerateCiteKeyCommand() { - return new GenerateCitationKeySingleAction(entry, databaseContext, dialogService, preferences, undoManager); + return new GenerateCitationKeySingleAction( + entry, databaseContext, dialogService, preferences, undoManager); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java b/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java index 86b22ca86e5e..d83b66424fcd 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java +++ b/src/main/java/org/jabref/gui/fieldeditors/ContextMenuAddable.java @@ -1,17 +1,18 @@ package org.jabref.gui.fieldeditors; -import java.util.List; -import java.util.function.Supplier; - import javafx.scene.control.MenuItem; import org.jabref.gui.keyboard.KeyBindingRepository; +import java.util.List; +import java.util.function.Supplier; + 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 * allocations when the main table is just scrolled with the entry editor open. */ - void initContextMenu(final Supplier> items, KeyBindingRepository keyBindingRepository); + void initContextMenu( + final Supplier> items, KeyBindingRepository keyBindingRepository); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java b/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java index d4db223ab9d7..a495f435a074 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/DateEditor.java @@ -1,8 +1,8 @@ package org.jabref.gui.fieldeditors; -import java.time.format.DateTimeFormatter; +import com.airhacks.afterburner.views.ViewLoader; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.Parent; @@ -18,8 +18,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.time.format.DateTimeFormatter; + +import javax.swing.undo.UndoManager; public class DateEditor extends HBox implements FieldEditorFX { @@ -30,20 +31,29 @@ public class DateEditor extends HBox implements FieldEditorFX { @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); + 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() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java index d8bc6e37e123..aba609ec3072 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/DateEditorViewModel.java @@ -1,12 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.time.DateTimeException; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.time.temporal.TemporalAccessor; - -import javax.swing.undo.UndoManager; - import javafx.util.StringConverter; import org.jabref.gui.autocompleter.SuggestionProvider; @@ -14,16 +7,27 @@ import org.jabref.model.entry.Date; import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.DateTimeException; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; + +import javax.swing.undo.UndoManager; + 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; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java b/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java index 7b478f946951..ef61aa7a21ec 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java +++ b/src/main/java/org/jabref/gui/fieldeditors/EditorTextArea.java @@ -1,11 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.net.URL; -import java.util.List; -import java.util.Objects; -import java.util.ResourceBundle; -import java.util.function.Supplier; - import javafx.fxml.Initializable; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; @@ -15,15 +9,23 @@ import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; import org.jabref.gui.keyboard.KeyBindingRepository; +import java.net.URL; +import java.util.List; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.function.Supplier; + public class EditorTextArea extends TextArea implements Initializable, ContextMenuAddable { private final ContextMenu contextMenu = new ContextMenu(); + /** * Variable that contains user-defined behavior for paste action. */ - private PasteActionHandler pasteActionHandler = () -> { - // Set empty paste behavior by default - }; + private PasteActionHandler pasteActionHandler = + () -> { + // Set empty paste behavior by default + }; public EditorTextArea() { this(""); @@ -39,12 +41,16 @@ public EditorTextArea(final String text) { } @Override - public void initContextMenu(final Supplier> items, KeyBindingRepository keyBindingRepository) { - setOnContextMenuRequested(event -> { - contextMenu.getItems().setAll(EditorContextAction.getDefaultContextMenuItems(this)); - contextMenu.getItems().addAll(0, items.get()); - contextMenu.show(this, event.getScreenX(), event.getScreenY()); - }); + public void initContextMenu( + final Supplier> items, KeyBindingRepository keyBindingRepository) { + setOnContextMenuRequested( + event -> { + contextMenu + .getItems() + .setAll(EditorContextAction.getDefaultContextMenuItems(this)); + contextMenu.getItems().addAll(0, items.get()); + contextMenu.show(this, event.getScreenX(), event.getScreenY()); + }); } @Override diff --git a/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java b/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java index 34fd2be09f47..050758c85b52 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java +++ b/src/main/java/org/jabref/gui/fieldeditors/EditorTextField.java @@ -1,10 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.net.URL; -import java.util.List; -import java.util.ResourceBundle; -import java.util.function.Supplier; - import javafx.fxml.Initializable; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; @@ -16,6 +11,11 @@ import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; import org.jabref.gui.keyboard.KeyBindingRepository; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; +import java.util.function.Supplier; + public class EditorTextField extends TextField implements Initializable, ContextMenuAddable { private final ContextMenu contextMenu = new ContextMenu(); @@ -35,12 +35,16 @@ public EditorTextField(final String text) { } @Override - public void initContextMenu(final Supplier> items, KeyBindingRepository keyBindingRepository) { - setOnContextMenuRequested(event -> { - contextMenu.getItems().setAll(EditorContextAction.getDefaultContextMenuItems(this)); - contextMenu.getItems().addAll(0, items.get()); - contextMenu.show(this, event.getScreenX(), event.getScreenY()); - }); + public void initContextMenu( + final Supplier> items, KeyBindingRepository keyBindingRepository) { + setOnContextMenuRequested( + event -> { + contextMenu + .getItems() + .setAll(EditorContextAction.getDefaultContextMenuItems(this)); + contextMenu.getItems().addAll(0, items.get()); + contextMenu.show(this, event.getScreenX(), event.getScreenY()); + }); } @Override diff --git a/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java b/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java index 64a5ff7e4cf2..29ea53c006e2 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java +++ b/src/main/java/org/jabref/gui/fieldeditors/EditorValidator.java @@ -1,13 +1,13 @@ package org.jabref.gui.fieldeditors; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + import javafx.scene.control.TextInputControl; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.util.IconValidationDecorator; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - public class EditorValidator { private final GuiPreferences preferences; @@ -16,7 +16,8 @@ public EditorValidator(GuiPreferences preferences) { this.preferences = preferences; } - public void configureValidation(final ValidationStatus status, final TextInputControl textInput) { + public void configureValidation( + final ValidationStatus status, final TextInputControl textInput) { if (preferences.getEntryEditorPreferences().shouldEnableValidation()) { ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); validationVisualizer.setDecoration(new IconValidationDecorator()); diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java b/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java index 8371ccbfa2ff..aa59e7d83dd8 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java +++ b/src/main/java/org/jabref/gui/fieldeditors/FieldEditorFX.java @@ -1,7 +1,8 @@ package org.jabref.gui.fieldeditors; -import java.util.Arrays; -import java.util.List; +import com.github.difflib.DiffUtils; +import com.github.difflib.patch.AbstractDelta; +import com.tobiasdiez.easybind.EasyBind; import javafx.application.Platform; import javafx.beans.property.StringProperty; @@ -15,13 +16,12 @@ import org.jabref.gui.undo.UndoAction; import org.jabref.gui.util.UiTaskExecutor; import org.jabref.model.entry.BibEntry; - -import com.github.difflib.DiffUtils; -import com.github.difflib.patch.AbstractDelta; -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; +import java.util.List; + public interface FieldEditorFX { void bindToEntry(BibEntry entry); @@ -29,40 +29,59 @@ public interface FieldEditorFX { /** * @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. + // 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 - && e.isShortcutDown()) { - if (keyBindingRepository.matches(e, KeyBinding.UNDO)) { - undoAction.execute(); - e.consume(); - } else if (keyBindingRepository.matches(e, KeyBinding.REDO)) { - redoAction.execute(); - e.consume(); - } - } - }); + 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 + && e.isShortcutDown()) { + if (keyBindingRepository.matches(e, KeyBinding.UNDO)) { + undoAction.execute(); + e.consume(); + } else if (keyBindingRepository.matches(e, KeyBinding.REDO)) { + redoAction.execute(); + e.consume(); + } + } + }); // We need some more sophisticated handling to avoid cursor jumping // 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 - if (Platform.isFxApplicationThread()) { - setTextAndUpdateCaretPosition(textInputControl, newText, logger); - } else { - UiTaskExecutor.runInJavaFXThread(() -> setTextAndUpdateCaretPosition(textInputControl, newText, logger)); - } - }); + 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 + if (Platform.isFxApplicationThread()) { + setTextAndUpdateCaretPosition(textInputControl, newText, logger); + } else { + UiTaskExecutor.runInJavaFXThread( + () -> + setTextAndUpdateCaretPosition( + textInputControl, newText, logger)); + } + }); EasyBind.subscribe(textInputControl.textProperty(), viewModelTextProperty::set); } - private void setTextAndUpdateCaretPosition(TextInputControl textInputControl, String newText, Logger logger) { + private void setTextAndUpdateCaretPosition( + TextInputControl textInputControl, String newText, Logger logger) { int lastCaretPosition = textInputControl.getCaretPosition(); logger.trace("Caret at position {}", lastCaretPosition); String oldText = textInputControl.getText(); @@ -85,7 +104,8 @@ private void setTextAndUpdateCaretPosition(TextInputControl textInputControl, St // In this case, we want to adjust the caret position List oldValueCharacters = Arrays.asList(oldText.split("")); List newValueCharacters = Arrays.asList(newText.split("")); - List> deltaList = DiffUtils.diff(oldValueCharacters, newValueCharacters).getDeltas(); + List> deltaList = + DiffUtils.diff(oldValueCharacters, newValueCharacters).getDeltas(); logger.trace("Deltas: {}", deltaList); AbstractDelta lastDelta = null; for (AbstractDelta delta : deltaList) { @@ -129,11 +149,7 @@ 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(); } /** diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java index 643ca3179e35..a2ed7a613f29 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java +++ b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java @@ -1,11 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Set; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.autocompleter.ContentSelectorSuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProvider; @@ -35,106 +29,198 @@ import org.jabref.model.entry.types.EntryType; import org.jabref.model.entry.types.IEEETranEntryType; import org.jabref.model.metadata.MetaData; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Set; + +import javax.swing.undo.UndoManager; + @SuppressWarnings("unchecked") public class FieldEditors { private static final Logger LOGGER = LoggerFactory.getLogger(FieldEditors.class); - public static FieldEditorFX getForField(final Field field, - final TaskExecutor taskExecutor, - final DialogService dialogService, - final JournalAbbreviationRepository journalAbbreviationRepository, - final GuiPreferences preferences, - final BibDatabaseContext databaseContext, - final EntryType entryType, - final SuggestionProviders suggestionProviders, - final UndoManager undoManager, - final UndoAction undoAction, - final RedoAction redoAction) { + public static FieldEditorFX getForField( + final Field field, + final TaskExecutor taskExecutor, + final DialogService dialogService, + 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(), - journalAbbreviationRepository, - preferences.getEntryEditorPreferences().shouldAllowIntegerEditionBibtex()); + 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); + 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); + 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)) { - return new JournalEditor(field, suggestionProvider, fieldCheckers, undoAction, redoAction); - } else if (fieldProperties.contains(FieldProperty.IDENTIFIER) && field != StandardField.PMID || field == StandardField.ISBN) { + 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 return new IdentifierEditor(field, suggestionProvider, fieldCheckers); } else if (field == StandardField.ISSN) { return new ISSNEditor(field, suggestionProvider, fieldCheckers, undoAction, redoAction); } else if (field == StandardField.OWNER) { - return new OwnerEditor(field, suggestionProvider, fieldCheckers, undoAction, redoAction); + return new OwnerEditor( + field, suggestionProvider, fieldCheckers, undoAction, redoAction); } else if (field == StandardField.GROUPS) { - return new GroupEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, undoAction, redoAction); + 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)) { - return new OptionEditor<>(new YesNoEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + 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)); + return new OptionEditor<>( + new MonthEditorViewModel( + field, + suggestionProvider, + databaseContext.getMode(), + fieldCheckers, + undoManager)); } else if (field == StandardField.GENDER) { - return new OptionEditor<>(new GenderEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + 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)); + return new OptionEditor<>( + new EditorTypeEditorViewModel( + field, suggestionProvider, fieldCheckers, undoManager)); } else if (fieldProperties.contains(FieldProperty.PAGINATION)) { - return new OptionEditor<>(new PaginationEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + 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)); + return new OptionEditor<>( + new PatentTypeEditorViewModel( + field, suggestionProvider, fieldCheckers, undoManager)); } else { - return new OptionEditor<>(new TypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); + 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.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); + 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); + 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); + 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); + List selectorValues = + databaseContext.getMetaData().getContentSelectorValuesForField(field); if (!isMultiLine && !selectorValues.isEmpty()) { - return new OptionEditor<>(new CustomFieldEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager, selectorValues)); + return new OptionEditor<>( + new CustomFieldEditorViewModel( + field, + suggestionProvider, + fieldCheckers, + undoManager, + selectorValues)); } else { - return new SimpleEditor(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, undoAction, redoAction); + 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); + return new ContentSelectorSuggestionProvider( + (SuggestionProvider) suggestionProvider, contentSelectorValues); } catch (ClassCastException exception) { - LOGGER.error("Content selectors are only supported for normal fields with string-based auto completion."); + LOGGER.error( + "Content selectors are only supported for normal fields with string-based auto completion."); return suggestionProvider; } } else { diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java b/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java index 33c9018a829d..f5ac5745c7ff 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldNameLabel.java +++ b/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: @@ -55,14 +58,17 @@ public String getDescription(Field field) { case BOOKTITLE: return Localization.lang("Title of the main publication this work is part of."); case BOOKTITLEADDON: - return Localization.lang("Annex to the \"Booktitle\", to be printed in a different font."); + return Localization.lang( + "Annex to the \"Booktitle\", to be printed in a different font."); case CHAPTER: return Localization.lang("Chapter or section or any other unit of a work."); 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,49 +76,71 @@ 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."); + return Localization.lang( + "Another secondary editor performing a different role."); case EDITORC: - return Localization.lang("Another secondary editor performing a different role."); + return Localization.lang( + "Another secondary editor performing a different role."); case EDITORTYPE: return Localization.lang("Type of editorial role performed by the \"Editor\"."); case EDITORATYPE: - return Localization.lang("Type of editorial role performed by the \"Editora\"."); + return Localization.lang( + "Type of editorial role performed by the \"Editora\"."); case EDITORBTYPE: - return Localization.lang("Type of editorial role performed by the \"Editorb\"."); + return Localization.lang( + "Type of editorial role performed by the \"Editorb\"."); case EDITORCTYPE: - return Localization.lang("Type of editorial role performed by the \"Editorc\"."); + 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."); + 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."); + 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."); 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."); + 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."); @@ -121,65 +149,90 @@ public String getDescription(Field field) { case ISBN: return Localization.lang("International Standard Book Number of a book."); case ISRN: - return Localization.lang("International Standard Technical Report Number of a technical report."); + return Localization.lang( + "International Standard Technical Report Number of a technical report."); case ISSN: - return Localization.lang("International Standard Serial Number of a periodical."); + 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."); + return Localization.lang( + "Subtitle of a specific issue of a journal or other periodical."); case ISSUETITLE: - return Localization.lang("Title of a specific issue of a journal or other periodical."); + return Localization.lang( + "Title of a specific issue of a journal or other periodical."); case JOURNALSUBTITLE: - return Localization.lang("Subtitle of a journal, a newspaper, or some other periodical."); + return Localization.lang( + "Subtitle of a journal, a newspaper, or some other periodical."); case JOURNALTITLE: case JOURNAL: - return Localization.lang("Name of a journal, a newspaper, or some other periodical."); + 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."); + 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: - return Localization.lang("Main title of a multi-volume book, such as \"Collected Works\"."); + return Localization.lang( + "Main title of a multi-volume book, such as \"Collected Works\"."); case MAINTITLEADDON: - return Localization.lang("Annex to the \"Maintitle\", to be printed in a different font."); + return Localization.lang( + "Annex to the \"Maintitle\", to be printed in a different font."); 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."); + 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."); + 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."); + 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: @@ -187,19 +240,25 @@ public String getDescription(Field field) { case TITLE: return Localization.lang("Title of the work."); case TITLEADDON: - return Localization.lang("Annex to the \"Title\", to be printed in a different font."); + 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\"."); + return Localization.lang( + "Type of a \"Manual\", \"Patent\", \"Report\", or \"Thesis\"."); case URL: return Localization.lang("URL of an online publication."); case URLDATE: - return Localization.lang("Access date of the address specified in the url field."); + return Localization.lang( + "Access date of the address specified in the url field."); case VENUE: - return Localization.lang("Location of a conference, a symposium, or some other event."); + return Localization.lang( + "Location of a conference, a symposium, or some other event."); case VERSION: - return Localization.lang("Revision number of a piece of software, a manual, etc."); + return Localization.lang( + "Revision number of a piece of software, a manual, etc."); case VOLUME: return Localization.lang("Volume of a multi-volume book or a periodical."); case VOLUMES: @@ -207,21 +266,27 @@ 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."); + return Localization.lang( + "Gender of the author or gender of the editor, if there is no author."); case KEYWORDS: return Localization.lang("Separated list of keywords."); case RELATED: - return Localization.lang("Citation keys of other entries which have a relationship to this entry."); + 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."); + return Localization.lang( + "Name(s) of the (manual) groups the entry belongs to."); case OWNER: return Localization.lang("Owner/creator of this entry."); case TIMESTAMP: - return Localization.lang("Timestamp of this entry, when it has been created or last modified."); + return Localization.lang( + "Timestamp of this entry, when it has been created or last modified."); } } else if (field instanceof InternalField internalField) { switch (internalField) { @@ -231,17 +296,20 @@ public String getDescription(Field field) { } else if (field instanceof SpecialField specialField) { switch (specialField) { case PRINTED: - return Localization.lang("User-specific printed flag, in case the entry has been printed."); + return Localization.lang( + "User-specific printed flag, in case the entry has been printed."); case PRIORITY: return Localization.lang("User-specific priority."); case QUALITY: - return Localization.lang("User-specific quality flag, in case its quality is assured."); + return Localization.lang( + "User-specific quality flag, in case its quality is assured."); case RANKING: return Localization.lang("User-specific ranking."); case READ_STATUS: return Localization.lang("User-specific read status."); case RELEVANCE: - return Localization.lang("User-specific relevance flag, in case the entry is relevant."); + return Localization.lang( + "User-specific relevance flag, in case the entry is relevant."); } } return ""; diff --git a/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java b/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java index 16a50521b656..b37e1480cebf 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/GroupEditor.java @@ -1,10 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import javafx.scene.input.TransferMode; import org.jabref.gui.DragAndDropDataFormats; @@ -17,43 +12,74 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + 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) { - super(field, suggestionProvider, fieldCheckers, preferences, isMultiLine, undoManager, undoAction, 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 -> { - if (event.getDragboard().hasContent(DragAndDropDataFormats.GROUP)) { - event.acceptTransferModes(TransferMode.MOVE); - } - event.consume(); - }); + this.setOnDragOver( + event -> { + if (event.getDragboard().hasContent(DragAndDropDataFormats.GROUP)) { + event.acceptTransferModes(TransferMode.MOVE); + } + event.consume(); + }); - this.setOnDragDropped(event -> { - boolean success = false; - if (event.getDragboard().hasContent(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); - bibEntry.map(entry -> entry.setField(StandardField.GROUPS, newGroup)); - success = true; - } - } - event.setDropCompleted(success); - event.consume(); - }); + this.setOnDragDropped( + event -> { + boolean success = false; + if (event.getDragboard().hasContent(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); + bibEntry.map(entry -> entry.setField(StandardField.GROUPS, newGroup)); + success = true; + } + } + event.setDropCompleted(success); + event.consume(); + }); } @Override diff --git a/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java index e273faa16ce0..c7a8f367cd6e 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditor.java @@ -1,8 +1,8 @@ package org.jabref.gui.fieldeditors; -import java.util.Optional; +import com.airhacks.afterburner.views.ViewLoader; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.Parent; @@ -22,8 +22,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.util.Optional; + +import javax.swing.undo.UndoManager; public class ISSNEditor extends HBox implements FieldEditorFX { @FXML private ISSNEditorViewModel viewModel; @@ -40,29 +41,31 @@ public class ISSNEditor extends HBox implements FieldEditorFX { 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); - - establishBinding(textArea, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); + 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( + textArea, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); textArea.initContextMenu(new DefaultMenu(textArea), keyBindingRepository); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); + new EditorValidator(preferences) + .configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); } public ISSNEditorViewModel getViewModel() { @@ -92,7 +95,8 @@ private void fetchInformationByIdentifier() { @FXML private void showJournalInfo() { - if (JournalInfoOptInDialogHelper.isJournalInfoEnabled(dialogService, preferences.getEntryEditorPreferences())) { + if (JournalInfoOptInDialogHelper.isJournalInfoEnabled( + dialogService, preferences.getEntryEditorPreferences())) { viewModel.showJournalInfo(journalInfoButton); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java index b260019f2f94..5375699c6240 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/ISSNEditorViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; - import javafx.scene.control.Button; import org.jabref.gui.DialogService; @@ -16,6 +14,8 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import javax.swing.undo.UndoManager; + public class ISSNEditorViewModel extends AbstractEditorViewModel { private final TaskExecutor taskExecutor; private final DialogService dialogService; @@ -45,10 +45,17 @@ public void showJournalInfo(Button journalInfoButton) { } public void fetchBibliographyInformation(BibEntry bibEntry) { - stateManager.getActiveDatabase().ifPresentOrElse( - databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, undoManager) - .fetchAndMerge(bibEntry, StandardField.ISSN), - () -> dialogService.notify(Localization.lang("No library selected")) - ); + stateManager + .getActiveDatabase() + .ifPresentOrElse( + databaseContext -> + new FetchAndMergeEntry( + databaseContext, + taskExecutor, + preferences, + dialogService, + undoManager) + .fetchAndMerge(bibEntry, StandardField.ISSN), + () -> dialogService.notify(Localization.lang("No library selected"))); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java index 0c964ef95329..dba3161931e3 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalEditor.java @@ -1,6 +1,8 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.Parent; @@ -21,8 +23,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import javax.swing.undo.UndoManager; public class JournalEditor extends HBox implements FieldEditorFX { @@ -37,29 +38,32 @@ public class JournalEditor extends HBox implements FieldEditorFX { @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); + 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() { @@ -83,7 +87,8 @@ private void toggleAbbreviation() { @FXML private void showJournalInfo() { - if (JournalInfoOptInDialogHelper.isJournalInfoEnabled(dialogService, preferences.getEntryEditorPreferences())) { + if (JournalInfoOptInDialogHelper.isJournalInfoEnabled( + dialogService, preferences.getEntryEditorPreferences())) { viewModel.showJournalInfo(journalInfoButton); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java index 4fba9d2635e2..d17c158395a4 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalEditorViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; - import javafx.scene.control.Button; import org.jabref.gui.DialogService; @@ -12,6 +10,8 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; +import javax.swing.undo.UndoManager; + public class JournalEditorViewModel extends AbstractEditorViewModel { private final JournalAbbreviationRepository journalAbbreviationRepository; private final TaskExecutor taskExecutor; @@ -39,11 +39,15 @@ public void toggleAbbreviation() { // Ignore brackets when matching abbreviations. final String name = StringUtil.ignoreCurlyBracket(text.get()); - journalAbbreviationRepository.getNextAbbreviation(name).ifPresent(nextAbbreviation -> { - text.set(nextAbbreviation); - // TODO: Add undo - // panel.getUndoManager().addEdit(new UndoableFieldChange(entry, editor.getName(), text, nextAbbreviation)); - }); + journalAbbreviationRepository + .getNextAbbreviation(name) + .ifPresent( + nextAbbreviation -> { + text.set(nextAbbreviation); + // TODO: Add undo + // panel.getUndoManager().addEdit(new UndoableFieldChange(entry, + // editor.getName(), text, nextAbbreviation)); + }); } public void showJournalInfo(Button journalInfoButton) { diff --git a/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java b/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java index b1209ba95db9..62aef47a0d35 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java +++ b/src/main/java/org/jabref/gui/fieldeditors/JournalInfoOptInDialogHelper.java @@ -9,33 +9,43 @@ public class JournalInfoOptInDialogHelper { /** * 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) { + public static boolean isJournalInfoEnabled( + DialogService dialogService, EntryEditorPreferences preferences) { + if (preferences.shouldEnableJournalPopup() + == EntryEditorPreferences.JournalPopupEnabled.ENABLED) { return true; } - 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 - : EntryEditorPreferences.JournalPopupEnabled.DISABLED); + 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 + : 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 - : EntryEditorPreferences.JournalPopupEnabled.DISABLED); + preferences.setEnableJournalPopup( + journalInfoEnabled + ? EntryEditorPreferences.JournalPopupEnabled.ENABLED + : EntryEditorPreferences.JournalPopupEnabled.DISABLED); return journalInfoEnabled; } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java b/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java index 4da18163ab25..7da6fcafda37 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditor.java @@ -1,8 +1,9 @@ package org.jabref.gui.fieldeditors; -import java.util.Comparator; +import com.airhacks.afterburner.views.ViewLoader; +import com.dlsc.gemsfx.TagsField; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.beans.binding.Bindings; import javafx.css.PseudoClass; @@ -29,13 +30,13 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.Keyword; import org.jabref.model.entry.field.Field; - -import com.airhacks.afterburner.views.ViewLoader; -import com.dlsc.gemsfx.TagsField; -import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Comparator; + +import javax.swing.undo.UndoManager; + public class KeywordsEditor extends HBox implements FieldEditorFX { private static final Logger LOGGER = LoggerFactory.getLogger(KeywordsEditor.class); private static final PseudoClass FOCUSED = PseudoClass.getPseudoClass("focused"); @@ -48,46 +49,54 @@ public class KeywordsEditor extends HBox implements FieldEditorFX { @Inject private UndoManager undoManager; @Inject private ClipBoardManager clipBoardManager; - 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, - undoManager); + this.viewModel = + new KeywordsEditorViewModel( + field, suggestionProvider, fieldCheckers, preferences, undoManager); - keywordTagsField.setCellFactory(new ViewModelListCellFactory().withText(Keyword::get)); + keywordTagsField.setCellFactory( + new ViewModelListCellFactory().withText(Keyword::get)); keywordTagsField.setTagViewFactory(this::createTag); - keywordTagsField.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); + 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)); + keywordTagsField.setNewItemProducer( + searchText -> viewModel.getStringConverter().fromString(searchText)); keywordTagsField.setShowSearchIcon(false); 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 -> { - if (event.getText().equals(keywordSeparator)) { - keywordTagsField.commit(); - event.consume(); - } - }); - - Bindings.bindContentBidirectional(keywordTagsField.getTags(), viewModel.keywordListProperty()); + keywordTagsField + .getEditor() + .setOnKeyReleased( + event -> { + if (event.getText().equals(keywordSeparator)) { + keywordTagsField.commit(); + event.consume(); + } + }); + + Bindings.bindContentBidirectional( + keywordTagsField.getTags(), viewModel.keywordListProperty()); } private Node createTag(Keyword keyword) { @@ -98,11 +107,19 @@ 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); return tagLabel; } @@ -140,19 +157,21 @@ public void execute() { switch (command) { case COPY -> { clipBoardManager.setContent(keyword.get()); - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(keyword.get()))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(keyword.get()))); } case CUT -> { clipBoardManager.setContent(keyword.get()); - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(keyword.get()))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + 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/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java index e33b22098e2f..4c33437129d8 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/KeywordsEditorViewModel.java @@ -1,10 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.util.List; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; - import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; @@ -17,20 +12,25 @@ import org.jabref.model.entry.Keyword; import org.jabref.model.entry.KeywordList; import org.jabref.model.entry.field.Field; - import org.tinylog.Logger; +import java.util.List; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + public class KeywordsEditorViewModel extends AbstractEditorViewModel { 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); @@ -39,10 +39,7 @@ public KeywordsEditorViewModel(Field field, this.suggestionProvider = suggestionProvider; BindingsHelper.bindContentBidirectional( - keywordListProperty, - text, - this::serializeKeywords, - this::parseKeywords); + keywordListProperty, text, this::serializeKeywords, this::parseKeywords); } private String serializeKeywords(List keywords) { @@ -76,12 +73,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)) { diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java index 4b65ad78349f..4e09c51a92d3 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java @@ -1,8 +1,9 @@ package org.jabref.gui.fieldeditors; -import java.util.Comparator; +import com.airhacks.afterburner.views.ViewLoader; +import com.dlsc.gemsfx.TagsField; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.beans.binding.Bindings; import javafx.css.PseudoClass; @@ -32,13 +33,13 @@ import org.jabref.model.entry.EntryLinkList; import org.jabref.model.entry.ParsedEntryLink; import org.jabref.model.entry.field.Field; - -import com.airhacks.afterburner.views.ViewLoader; -import com.dlsc.gemsfx.TagsField; -import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Comparator; + +import javax.swing.undo.UndoManager; + public class LinkedEntriesEditor extends HBox implements FieldEditorFX { private static final Logger LOGGER = LoggerFactory.getLogger(LinkedEntriesEditor.class); private static final PseudoClass FOCUSED = PseudoClass.getPseudoClass("focused"); @@ -51,37 +52,60 @@ public class LinkedEntriesEditor extends HBox implements FieldEditorFX { @Inject private UndoManager undoManager; @Inject private StateManager stateManager; - public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { - ViewLoader.view(this) - .root(this) - .load(); - - this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers, undoManager, stateManager); - - entryLinkField.setCellFactory(new ViewModelListCellFactory().withText(ParsedEntryLink::getKey)); - entryLinkField.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); + public LinkedEntriesEditor( + Field field, + BibDatabaseContext databaseContext, + SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers) { + ViewLoader.view(this).root(this).load(); + + 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.setNewItemProducer( + searchText -> viewModel.getStringConverter().fromString(searchText)); + 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 -> { - if (event.getText().equals(separator)) { - entryLinkField.commit(); - event.consume(); - } - }); - - Bindings.bindContentBidirectional(entryLinkField.getTags(), viewModel.linkedEntriesProperty()); + entryLinkField + .getEditor() + .setOnKeyReleased( + event -> { + if (event.getText().equals(separator)) { + entryLinkField.commit(); + event.consume(); + } + }); + + Bindings.bindContentBidirectional( + entryLinkField.getTags(), viewModel.linkedEntriesProperty()); } private Node createTag(ParsedEntryLink entryLink) { @@ -90,19 +114,28 @@ private Node createTag(ParsedEntryLink entryLink) { tagLabel.setGraphic(IconTheme.JabRefIcons.REMOVE_TAGS.getGraphicNode()); tagLabel.getGraphic().setOnMouseClicked(event -> entryLinkField.removeTags(entryLink)); tagLabel.setContentDisplay(ContentDisplay.RIGHT); - tagLabel.setOnMouseClicked(event -> { - if ((event.getClickCount() == 2 || event.isControlDown()) && event.getButton() == MouseButton.PRIMARY) { - viewModel.jumpToEntry(entryLink); - } - }); + tagLabel.setOnMouseClicked( + event -> { + if ((event.getClickCount() == 2 || event.isControlDown()) + && event.getButton() == MouseButton.PRIMARY) { + viewModel.jumpToEntry(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; } @@ -135,19 +168,21 @@ public void execute() { switch (command) { case COPY -> { clipBoardManager.setContent(entryLink.getKey()); - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(entryLink.getKey()))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + JabRefDialogService.shortenDialogMessage(entryLink.getKey()))); } case CUT -> { clipBoardManager.setContent(entryLink.getKey()); - dialogService.notify(Localization.lang("Copied '%0' to clipboard.", - JabRefDialogService.shortenDialogMessage(entryLink.getKey()))); + dialogService.notify( + Localization.lang( + "Copied '%0' to clipboard.", + 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/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java index a61599e9a30a..65e4e49a656d 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java @@ -1,11 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; - import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; @@ -21,6 +15,12 @@ import org.jabref.model.entry.ParsedEntryLink; import org.jabref.model.entry.field.Field; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + public class LinkedEntriesEditorViewModel extends AbstractEditorViewModel { private final BibDatabaseContext databaseContext; @@ -28,12 +28,13 @@ public class LinkedEntriesEditorViewModel extends AbstractEditorViewModel { 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; @@ -70,14 +71,24 @@ 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 +99,13 @@ 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/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index 7d72252ada2b..92ed8c712a0f 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -1,14 +1,9 @@ package org.jabref.gui.fieldeditors; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.BiPredicate; -import java.util.function.Supplier; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.Observable; import javafx.beans.binding.Bindings; @@ -50,14 +45,19 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.OptionalUtil; - -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.BiPredicate; +import java.util.function.Supplier; + public class LinkedFileViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(LinkedFileViewModel.class); @@ -78,34 +78,46 @@ public class LinkedFileViewModel extends AbstractViewModel { 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()))); - - downloadOngoing.bind(downloadProgress.greaterThanOrEqualTo(0).and(downloadProgress.lessThan(1))); - isOfflinePdf.setValue(!linkedFile.isOnlineLink() && "pdf".equalsIgnoreCase(linkedFile.getFileType())); + 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( @@ -116,12 +128,7 @@ public static LinkedFileViewModel fromLinkedFile( DialogService dialogService, GuiPreferences preferences) { return new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + linkedFile, entry, databaseContext, taskExecutor, dialogService, preferences); } public BooleanProperty isOfflinePdfProperty() { @@ -166,13 +173,18 @@ public String getDescriptionAndLink() { public String getTruncatedDescriptionAndLink() { if (StringUtil.isBlank(linkedFile.getDescription())) { - return ControlHelper.truncateString(linkedFile.getLink(), -1, "...", - ControlHelper.EllipsisPosition.CENTER); + 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) + ")"; + return ControlHelper.truncateString( + linkedFile.getDescription(), + -1, + "...", + ControlHelper.EllipsisPosition.CENTER) + + " (" + + ControlHelper.truncateString( + linkedFile.getLink(), -1, "...", ControlHelper.EllipsisPosition.CENTER) + + ")"; } } @@ -181,14 +193,18 @@ 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; @@ -212,30 +228,46 @@ 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) { - dialogService.showErrorDialogAndWait(Localization.lang("Error opening file '%0'", linkedFile.getLink()), e); + dialogService.showErrorDialogAndWait( + Localization.lang("Error opening file '%0'", linkedFile.getLink()), e); } } public void openFolder() { try { if (!linkedFile.isOnlineLink()) { - Optional resolvedPath = FileUtil.find( - databaseContext, - linkedFile.getLink(), - preferences.getFilePreferences()); + Optional resolvedPath = + FileUtil.find( + databaseContext, + linkedFile.getLink(), + preferences.getFilePreferences()); if (resolvedPath.isPresent()) { - NativeDesktop.openFolderAndSelectFile(resolvedPath.get(), preferences.getExternalApplicationsPreferences(), dialogService); + 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.")); + dialogService.showErrorDialogAndWait( + Localization.lang("Cannot open folder as the file is an online link.")); } } catch (IOException ex) { LOGGER.debug("Cannot open folder", ex); @@ -249,10 +281,11 @@ public void renameToSuggestion() { 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"), - oldFilePath.getFileName().toString()); + Optional askedFileName = + dialogService.showInputDialogWithDefaultAndWait( + Localization.lang("Rename file"), + Localization.lang("New Filename"), + oldFilePath.getFileName().toString()); askedFileName.ifPresent(this::renameFileToName); } @@ -266,19 +299,23 @@ public void renameFileToName(String targetFileName) { if (file.isPresent()) { performRenameWithConflictCheck(targetFileName); } else { - 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())); } } private void performRenameWithConflictCheck(String targetFileName) { - Optional existingFile = linkedFileHandler.findExistingFile(linkedFile, entry, targetFileName); + Optional existingFile = + linkedFileHandler.findExistingFile(linkedFile, entry, targetFileName); boolean overwriteFile = false; if (existingFile.isPresent()) { - overwriteFile = dialogService.showConfirmationDialogAndWait( - Localization.lang("File exists"), - Localization.lang("'%0' exists. Overwrite file?", targetFileName), - Localization.lang("Overwrite")); + overwriteFile = + dialogService.showConfirmationDialogAndWait( + Localization.lang("File exists"), + Localization.lang("'%0' exists. Overwrite file?", targetFileName), + Localization.lang("Overwrite")); if (!overwriteFile) { return; @@ -288,7 +325,10 @@ private void performRenameWithConflictCheck(String targetFileName) { try { linkedFileHandler.renameToName(targetFileName, overwriteFile); } catch (IOException e) { - dialogService.showErrorDialogAndWait(Localization.lang("Rename failed"), Localization.lang("JabRef cannot access the file because it is being used by another process.")); + dialogService.showErrorDialogAndWait( + Localization.lang("Rename failed"), + Localization.lang( + "JabRef cannot access the file because it is being used by another process.")); } } @@ -299,9 +339,12 @@ public void moveToDefaultDirectory() { } // Get target folder - Optional fileDir = databaseContext.getFirstExistingFileDir(preferences.getFilePreferences()); + 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; } @@ -318,7 +361,9 @@ public void moveToDefaultDirectory() { } } 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())); } } @@ -349,26 +394,31 @@ public boolean isGeneratedPathSameAsOriginal() { } // append File directory pattern if exits - String targetDirectoryName = FileUtil.createDirNameFromPattern( - databaseContext.getDatabase(), - entry, - filePreferences.getFileDirectoryPattern()); + 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; } - BiPredicate equality = (fileA, fileB) -> { - try { - return Files.isSameFile(fileA, fileB); - } catch (IOException e) { - return false; - } - }; + BiPredicate equality = + (fileA, fileB) -> { + try { + return Files.isSameFile(fileA, fileB); + } catch (IOException e) { + return false; + } + }; return OptionalUtil.equals(targetDir, currentDir, equality); } @@ -385,19 +435,27 @@ public void moveToDefaultDirectoryAndRename() { * 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)); - editedFile.ifPresent(file -> { - this.linkedFile.setLink(file.getLink()); - this.linkedFile.setDescription(file.getDescription()); - this.linkedFile.setFileType(file.getFileType()); - this.linkedFile.setSourceURL(file.getSourceUrl()); - }); + Optional editedFile = + dialogService.showCustomDialogAndWait(new LinkedFileEditDialog(this.linkedFile)); + editedFile.ifPresent( + file -> { + this.linkedFile.setLink(file.getLink()); + this.linkedFile.setDescription(file.getDescription()); + this.linkedFile.setFileType(file.getFileType()); + this.linkedFile.setSourceURL(file.getSourceUrl()); + }); } /** @@ -405,23 +463,26 @@ public void edit() { */ 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"); + 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"); } 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(); } @@ -429,20 +490,22 @@ public void redownload() { public void download(boolean keepHtmlLink) { LOGGER.info("Downloading file from {}", linkedFile.getSourceUrl()); if (!linkedFile.isOnlineLink()) { - throw new UnsupportedOperationException("In order to download the file it has to be an online link"); + 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(); } @@ -456,29 +519,59 @@ public ValidationStatus fileExistsValidationStatus() { } public void parsePdfMetadataAndShowMergeDialog() { - linkedFile.findIn(databaseContext, preferences.getFilePreferences()).ifPresent(filePath -> { - MultiMergeEntriesView dialog = new MultiMergeEntriesView(preferences, taskExecutor); - dialog.setTitle(Localization.lang("Merge PDF metadata")); - dialog.addSource(Localization.lang("Entry"), entry); - dialog.addSource(Localization.lang("Verbatim"), wrapImporterToSupplier(new PdfVerbatimBibTextImporter(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(Localization.lang("XMP metadata"), wrapImporterToSupplier(new PdfXmpImporter(preferences.getXmpPreferences()), filePath)); - dialog.addSource(Localization.lang("Content"), wrapImporterToSupplier(new PdfContentImporter(), filePath)); - dialogService.showCustomDialogAndWait(dialog).ifPresent(newEntry -> { - databaseContext.getDatabase().removeEntry(entry); - databaseContext.getDatabase().insertEntry(newEntry); - }); - }); + linkedFile + .findIn(databaseContext, preferences.getFilePreferences()) + .ifPresent( + filePath -> { + MultiMergeEntriesView dialog = + new MultiMergeEntriesView(preferences, taskExecutor); + dialog.setTitle(Localization.lang("Merge PDF metadata")); + dialog.addSource(Localization.lang("Entry"), entry); + dialog.addSource( + Localization.lang("Verbatim"), + wrapImporterToSupplier( + new PdfVerbatimBibTextImporter( + 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( + Localization.lang("XMP metadata"), + wrapImporterToSupplier( + new PdfXmpImporter(preferences.getXmpPreferences()), + filePath)); + dialog.addSource( + Localization.lang("Content"), + wrapImporterToSupplier(new PdfContentImporter(), filePath)); + dialogService + .showCustomDialogAndWait(dialog) + .ifPresent( + newEntry -> { + databaseContext.getDatabase().removeEntry(entry); + databaseContext.getDatabase().insertEntry(newEntry); + }); + }); } private Supplier wrapImporterToSupplier(Importer importer, Path filePath) { return () -> { try { ParserResult parserResult = importer.importDatabase(filePath); - if (parserResult.isInvalid() || parserResult.isEmpty() || !parserResult.getDatabase().hasEntries()) { + if (parserResult.isInvalid() + || parserResult.isEmpty() + || !parserResult.getDatabase().hasEntries()) { return null; } return parserResult.getDatabase().getEntries().getFirst(); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index 5a95b3e42f27..a775b34edc50 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -1,8 +1,10 @@ package org.jabref.gui.fieldeditors; -import java.util.Optional; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.optional.ObservableOptionalValue; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleObjectProperty; @@ -60,10 +62,9 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.optional.ObservableOptionalValue; -import jakarta.inject.Inject; +import java.util.Optional; + +import javax.swing.undo.UndoManager; public class LinkedFilesEditor extends HBox implements FieldEditorFX { @@ -85,21 +86,21 @@ public class LinkedFilesEditor extends HBox implements FieldEditorFX { private LinkedFilesEditorViewModel viewModel; - private ObservableOptionalValue bibEntry = EasyBind.wrapNullable(new SimpleObjectProperty<>()); + private ObservableOptionalValue bibEntry = + EasyBind.wrapNullable(new SimpleObjectProperty<>()); private final UiThreadObservableList decoratedModelList; - 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); @@ -107,15 +108,16 @@ 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) @@ -136,17 +138,20 @@ 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); } } - private void handleOnDragDetected(@SuppressWarnings("unused") LinkedFileViewModel linkedFile, MouseEvent event) { + private void handleOnDragDetected( + @SuppressWarnings("unused") LinkedFileViewModel linkedFile, MouseEvent event) { LinkedFile selectedItem = listView.getSelectionModel().getSelectedItem().getFile(); 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); } @@ -160,7 +165,8 @@ private void handleOnDragDropped(LinkedFileViewModel originalItem, DragEvent eve ObservableList items = listView.itemsProperty().get(); if (dragboard.hasContent(DragAndDropDataFormats.LINKED_FILE)) { - LinkedFile linkedFile = (LinkedFile) dragboard.getContent(DragAndDropDataFormats.LINKED_FILE); + LinkedFile linkedFile = + (LinkedFile) dragboard.getContent(DragAndDropDataFormats.LINKED_FILE); LinkedFileViewModel transferredItem = null; int draggedIdx = 0; for (int i = 0; i < items.size(); i++) { @@ -189,7 +195,9 @@ 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,70 +212,97 @@ 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()); acceptAutoLinkedFile.getStyleClass().setAll("icon-button"); Button writeMetadataToPdf = IconTheme.JabRefIcons.PDF_METADATA_WRITE.asButton(); - writeMetadataToPdf.setTooltip(new Tooltip(Localization.lang("Write BibTeX to PDF (XMP and embedded)"))); + writeMetadataToPdf.setTooltip( + new Tooltip(Localization.lang("Write BibTeX to PDF (XMP and embedded)"))); 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 - ); - writeMetadataToPdf.disableProperty().bind(writeMetadataToSinglePdfAction.executableProperty().not()); + WriteMetadataToSinglePdfAction writeMetadataToSinglePdfAction = + new WriteMetadataToSinglePdfAction( + 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()); Button parsePdfMetadata = IconTheme.JabRefIcons.PDF_METADATA_READ.asButton(); parsePdfMetadata.setTooltip(new Tooltip(Localization.lang("Parse Metadata from PDF."))); parsePdfMetadata.visibleProperty().bind(linkedFile.isOfflinePdfProperty()); - parsePdfMetadata.setOnAction(event -> { - GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences()); - linkedFile.parsePdfMetadataAndShowMergeDialog(); - }); + parsePdfMetadata.setOnAction( + event -> { + GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided( + dialogService, preferences.getGrobidPreferences()); + linkedFile.parsePdfMetadataAndShowMergeDialog(); + }); parsePdfMetadata.getStyleClass().setAll("icon-button"); HBox container = new HBox(2); container.setPrefHeight(Double.NEGATIVE_INFINITY); container.maxWidthProperty().bind(listView.widthProperty().subtract(20d)); - container.getChildren().addAll(acceptAutoLinkedFile, info, writeMetadataToPdf, parsePdfMetadata); + container + .getChildren() + .addAll(acceptAutoLinkedFile, info, writeMetadataToPdf, parsePdfMetadata); return container; } private void setUpKeyBindings() { - listView.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - Optional keyBinding = preferences.getKeyBindingRepository().mapToKeyBinding(event); - if (keyBinding.isPresent()) { - switch (keyBinding.get()) { - case DELETE_ENTRY: - deleteAttachedFilesWithConfirmation(); - event.consume(); - break; - default: - // Pass other keys to children - } - } - }); + listView.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + Optional keyBinding = + preferences.getKeyBindingRepository().mapToKeyBinding(event); + if (keyBinding.isPresent()) { + switch (keyBinding.get()) { + case DELETE_ENTRY: + deleteAttachedFilesWithConfirmation(); + event.consume(); + break; + default: + // Pass other keys to children + } + } + }); } 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,9 +322,12 @@ public Parent getNode() { @FXML private void addNewFile() { - dialogService.showCustomDialogAndWait(new LinkedFileEditDialog()).ifPresent(newLinkedFile -> { - viewModel.addNewLinkedFile(newLinkedFile); - }); + dialogService + .showCustomDialogAndWait(new LinkedFileEditDialog()) + .ifPresent( + newLinkedFile -> { + viewModel.addNewLinkedFile(newLinkedFile); + }); } @FXML @@ -318,22 +356,69 @@ private ContextMenu createContextMenuForFile(LinkedFileViewModel linkedFile) { ContextMenu menu = new ContextMenu(); ActionFactory factory = new ActionFactory(); - menu.getItems().addAll( - factory.createMenuItem(StandardActions.EDIT_FILE_LINK, new ContextAction(StandardActions.EDIT_FILE_LINK, linkedFile, preferences)), - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.OPEN_FILE, new ContextAction(StandardActions.OPEN_FILE, linkedFile, preferences)), - factory.createMenuItem(StandardActions.OPEN_FOLDER, new ContextAction(StandardActions.OPEN_FOLDER, linkedFile, preferences)), - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.DOWNLOAD_FILE, new ContextAction(StandardActions.DOWNLOAD_FILE, linkedFile, preferences)), - factory.createMenuItem(StandardActions.RENAME_FILE_TO_PATTERN, new ContextAction(StandardActions.RENAME_FILE_TO_PATTERN, linkedFile, preferences)), - factory.createMenuItem(StandardActions.RENAME_FILE_TO_NAME, new ContextAction(StandardActions.RENAME_FILE_TO_NAME, linkedFile, preferences)), - factory.createMenuItem(StandardActions.MOVE_FILE_TO_FOLDER, new ContextAction(StandardActions.MOVE_FILE_TO_FOLDER, linkedFile, preferences)), - factory.createMenuItem(StandardActions.MOVE_FILE_TO_FOLDER_AND_RENAME, new ContextAction(StandardActions.MOVE_FILE_TO_FOLDER_AND_RENAME, linkedFile, preferences)), - factory.createMenuItem(StandardActions.COPY_FILE_TO_FOLDER, new CopySingleFileAction(linkedFile.getFile(), dialogService, databaseContext, preferences.getFilePreferences())), - factory.createMenuItem(StandardActions.REDOWNLOAD_FILE, new ContextAction(StandardActions.REDOWNLOAD_FILE, linkedFile, preferences)), - factory.createMenuItem(StandardActions.REMOVE_LINK, new ContextAction(StandardActions.REMOVE_LINK, linkedFile, preferences)), - factory.createMenuItem(StandardActions.DELETE_FILE, new ContextAction(StandardActions.DELETE_FILE, linkedFile, preferences)) - ); + menu.getItems() + .addAll( + factory.createMenuItem( + StandardActions.EDIT_FILE_LINK, + new ContextAction( + StandardActions.EDIT_FILE_LINK, linkedFile, preferences)), + new SeparatorMenuItem(), + factory.createMenuItem( + StandardActions.OPEN_FILE, + new ContextAction( + StandardActions.OPEN_FILE, linkedFile, preferences)), + factory.createMenuItem( + StandardActions.OPEN_FOLDER, + new ContextAction( + StandardActions.OPEN_FOLDER, linkedFile, preferences)), + new SeparatorMenuItem(), + factory.createMenuItem( + StandardActions.DOWNLOAD_FILE, + new ContextAction( + StandardActions.DOWNLOAD_FILE, linkedFile, preferences)), + factory.createMenuItem( + StandardActions.RENAME_FILE_TO_PATTERN, + new ContextAction( + StandardActions.RENAME_FILE_TO_PATTERN, + linkedFile, + preferences)), + factory.createMenuItem( + StandardActions.RENAME_FILE_TO_NAME, + new ContextAction( + StandardActions.RENAME_FILE_TO_NAME, + linkedFile, + preferences)), + factory.createMenuItem( + StandardActions.MOVE_FILE_TO_FOLDER, + new ContextAction( + StandardActions.MOVE_FILE_TO_FOLDER, + linkedFile, + preferences)), + factory.createMenuItem( + StandardActions.MOVE_FILE_TO_FOLDER_AND_RENAME, + new ContextAction( + StandardActions.MOVE_FILE_TO_FOLDER_AND_RENAME, + linkedFile, + preferences)), + factory.createMenuItem( + StandardActions.COPY_FILE_TO_FOLDER, + new CopySingleFileAction( + linkedFile.getFile(), + dialogService, + databaseContext, + preferences.getFilePreferences())), + factory.createMenuItem( + StandardActions.REDOWNLOAD_FILE, + new ContextAction( + StandardActions.REDOWNLOAD_FILE, linkedFile, preferences)), + factory.createMenuItem( + StandardActions.REMOVE_LINK, + new ContextAction( + StandardActions.REMOVE_LINK, linkedFile, preferences)), + factory.createMenuItem( + StandardActions.DELETE_FILE, + new ContextAction( + StandardActions.DELETE_FILE, linkedFile, preferences))); return menu; } @@ -343,32 +428,78 @@ private class ContextAction extends SimpleCommand { private final StandardActions command; private final LinkedFileViewModel linkedFile; - public ContextAction(StandardActions command, LinkedFileViewModel linkedFile, CliPreferences preferences) { + public ContextAction( + StandardActions command, + LinkedFileViewModel linkedFile, + CliPreferences preferences) { this.command = command; this.linkedFile = linkedFile; 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)); + 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); }); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java index 540e5603d366..1f4ff63bcdfe 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java @@ -1,18 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ReadOnlyBooleanProperty; @@ -47,27 +34,43 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + 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) { + public LinkedFilesEditorViewModel( + Field field, + SuggestionProvider suggestionProvider, + DialogService dialogService, + BibDatabaseContext databaseContext, + TaskExecutor taskExecutor, + FieldCheckers fieldCheckers, + GuiPreferences preferences, + UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); @@ -85,10 +88,11 @@ public LinkedFilesEditorViewModel(Field field, SuggestionProvider suggestionP 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()); + List filesToSerialize = + files.stream() + .filter(file -> !file.isAutomaticallyFound()) + .map(LinkedFileViewModel::getFile) + .collect(Collectors.toList()); return FileFieldWriter.getStringRepresentation(filesToSerialize); } @@ -99,37 +103,41 @@ private static String getStringRepresentation(List files) { * * 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()); } - public LinkedFileViewModel fromFile(Path file, ExternalApplicationsPreferences externalApplicationsPreferences) { - List fileDirectories = databaseContext.getFileDirectories(preferences.getFilePreferences()); + public LinkedFileViewModel fromFile( + Path file, ExternalApplicationsPreferences externalApplicationsPreferences) { + List fileDirectories = + databaseContext.getFileDirectories(preferences.getFilePreferences()); LinkedFile linkedFile = fromFile(file, fileDirectories, externalApplicationsPreferences); return new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + linkedFile, entry, databaseContext, taskExecutor, dialogService, preferences); } private List parseToFileViewModel(String stringValue) { return FileFieldParser.parse(stringValue).stream() - .map(linkedFile -> new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences)) - .collect(Collectors.toList()); + .map( + linkedFile -> + new LinkedFileViewModel( + linkedFile, + entry, + databaseContext, + taskExecutor, + dialogService, + preferences)) + .collect(Collectors.toList()); } public ObservableList getFiles() { @@ -141,23 +149,34 @@ public ListProperty filesProperty() { } public void addNewFile() { - Path workingDirectory = databaseContext.getFirstExistingFileDir(preferences.getFilePreferences()) - .orElse(preferences.getFilePreferences().getWorkingDirectory()); + Path workingDirectory = + databaseContext + .getFirstExistingFileDir(preferences.getFilePreferences()) + .orElse(preferences.getFilePreferences().getWorkingDirectory()); - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(workingDirectory) - .build(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .withInitialDirectory(workingDirectory) + .build(); - List fileDirectories = databaseContext.getFileDirectories(preferences.getFilePreferences()); - List selectedFiles = dialogService.showFileOpenDialogAndGetMultipleFiles(fileDialogConfiguration); + List fileDirectories = + databaseContext.getFileDirectories(preferences.getFilePreferences()); + List selectedFiles = + dialogService.showFileOpenDialogAndGetMultipleFiles(fileDialogConfiguration); for (Path fileToAdd : selectedFiles) { 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), - Localization.lang("Rename and add")); + 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), + Localization.lang("Rename and add")); if (correctButtonPressed) { Path correctPath = fileToAdd.resolveSibling(newFilename); @@ -176,17 +195,22 @@ public void addNewFile() { } 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)); } private void addNewLinkedFile(Path correctPath, List fileDirectories) { - LinkedFile newLinkedFile = fromFile(correctPath, fileDirectories, preferences.getExternalApplicationsPreferences()); + LinkedFile newLinkedFile = + fromFile( + correctPath, + fileDirectories, + preferences.getExternalApplicationsPreferences()); addNewLinkedFile(newLinkedFile); } @@ -196,14 +220,15 @@ public void bindToEntry(BibEntry entry) { if ((entry != null) && 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); - } - }); + BackgroundTask> findAssociatedNotLinkedFiles = + BackgroundTask.wrap(() -> findAssociatedNotLinkedFiles(entry)) + .onSuccess( + list -> { + if (!list.isEmpty()) { + LOGGER.debug("Found non-associated files:", list); + files.addAll(list); + } + }); taskExecutor.execute(findAssociatedNotLinkedFiles); } } @@ -214,21 +239,23 @@ public void bindToEntry(BibEntry entry) { private List findAssociatedNotLinkedFiles(BibEntry entry) { List result = new ArrayList<>(); - AutoSetFileLinksUtil util = new AutoSetFileLinksUtil( - databaseContext, - preferences.getExternalApplicationsPreferences(), - preferences.getFilePreferences(), - preferences.getAutoLinkPreferences()); + 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); } @@ -236,7 +263,8 @@ private List findAssociatedNotLinkedFiles(BibEntry entry) { dialogService.showErrorDialogAndWait("Error accessing the file system", e); } - LOGGER.trace("Found {} associated files for entry {}", result.size(), entry.getCitationKey()); + LOGGER.trace( + "Found {} associated files for entry {}", result.size(), entry.getCitationKey()); return result; } @@ -246,51 +274,52 @@ public boolean downloadFile(String urlText) { addFromURLAndDownload(url); return true; } catch (MalformedURLException exception) { - dialogService.showErrorDialogAndWait( - Localization.lang("Invalid URL"), - exception); + dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); return false; } } public void fetchFulltext() { - FulltextFetchers fetcher = new FulltextFetchers( - preferences.getImportFormatPreferences(), - preferences.getImporterPreferences()); + FulltextFetchers fetcher = + new FulltextFetchers( + preferences.getImportFormatPreferences(), + preferences.getImporterPreferences()); Optional urlField = entry.getField(StandardField.URL); boolean download_success = false; if (urlField.isPresent()) { download_success = downloadFile(urlField.get()); } if (urlField.isEmpty() || !download_success) { - BackgroundTask - .wrap(() -> fetcher.findFullTextPDF(entry)) - .onRunning(() -> fulltextLookupInProgress.setValue(true)) - .onFinished(() -> fulltextLookupInProgress.setValue(false)) - .onSuccess(url -> { - if (url.isPresent()) { - addFromURLAndDownload(url.get()); - } else { - dialogService.notify(Localization.lang("No full text document found")); - } - }) - .executeWith(taskExecutor); + BackgroundTask.wrap(() -> fetcher.findFullTextPDF(entry)) + .onRunning(() -> fulltextLookupInProgress.setValue(true)) + .onFinished(() -> fulltextLookupInProgress.setValue(false)) + .onSuccess( + url -> { + if (url.isPresent()) { + addFromURLAndDownload(url.get()); + } else { + dialogService.notify( + Localization.lang("No full text document found")); + } + }) + .executeWith(taskExecutor); } } 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); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java b/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java index 891757d43ca0..6e3f1cda45af 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/MarkdownEditor.java @@ -1,6 +1,6 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; +import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; import javafx.scene.control.TextInputControl; @@ -12,14 +12,30 @@ import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.field.Field; -import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; +import javax.swing.undo.UndoManager; 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) { - super(field, suggestionProvider, fieldCheckers, preferences, true, undoManager, undoAction, redoAction); + private final FlexmarkHtmlConverter flexmarkHtmlConverter = + FlexmarkHtmlConverter.builder().build(); + + 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); } @Override diff --git a/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java b/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java index 01927ce52890..ecf249acc297 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/OwnerEditor.java @@ -1,6 +1,8 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.Parent; @@ -16,8 +18,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import javax.swing.undo.UndoManager; public class OwnerEditor extends HBox implements FieldEditorFX { @@ -28,19 +29,22 @@ public class OwnerEditor extends HBox implements FieldEditorFX { @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(); + 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(textArea, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); + this.viewModel = + new OwnerEditorViewModel( + field, suggestionProvider, preferences, fieldCheckers, undoManager); + establishBinding( + textArea, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); textArea.initContextMenu(EditorMenus.getNameMenu(textArea), keyBindingRepository); - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); + new EditorValidator(preferences) + .configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); } public OwnerEditorViewModel getViewModel() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java index 5334c20b7318..86c958f10a54 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/OwnerEditorViewModel.java @@ -1,20 +1,21 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; - import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.entry.field.Field; +import javax.swing.undo.UndoManager; + 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; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java b/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java index 8a15574dee40..d0edfaa75ce3 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/PersonsEditor.java @@ -1,6 +1,6 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; import javafx.scene.Parent; import javafx.scene.control.TextInputControl; @@ -18,7 +18,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.injection.Injector; +import javax.swing.undo.UndoManager; public class PersonsEditor extends HBox implements FieldEditorFX { @@ -26,24 +26,39 @@ public class PersonsEditor extends HBox implements FieldEditorFX { 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); + 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 diff --git a/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java index fb995bef63f0..6f7026142886 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/PersonsEditorViewModel.java @@ -1,11 +1,8 @@ package org.jabref.gui.fieldeditors; -import java.util.Collection; - -import javax.swing.undo.UndoManager; - import javafx.util.StringConverter; +import org.controlsfx.control.textfield.AutoCompletionBinding; import org.jabref.gui.autocompleter.AppendPersonNamesStrategy; import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.AutoCompletionStrategy; @@ -15,13 +12,20 @@ import org.jabref.model.entry.Author; import org.jabref.model.entry.field.Field; -import org.controlsfx.control.textfield.AutoCompletionBinding; +import java.util.Collection; + +import javax.swing.undo.UndoManager; 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; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java b/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java index 4989adf09181..9c63fb97487a 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java +++ b/src/main/java/org/jabref/gui/fieldeditors/PopOverUtil.java @@ -1,10 +1,9 @@ package org.jabref.gui.fieldeditors; -import java.util.Optional; - import javafx.scene.control.Button; import javafx.scene.control.ProgressIndicator; +import org.controlsfx.control.PopOver; import org.jabref.gui.DialogService; import org.jabref.gui.fieldeditors.journalinfo.JournalInfoView; import org.jabref.logic.l10n.Localization; @@ -13,11 +12,12 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; -import org.controlsfx.control.PopOver; +import java.util.Optional; 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); @@ -32,21 +32,31 @@ public static void showJournalInfo(Button button, BibEntry entry, DialogService popOver.setArrowSize(0); 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); - }) + 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 { - dialogService.notify(Localization.lang("ISSN or journal name required for fetching journal information")); + dialogService.notify( + Localization.lang( + "ISSN or journal name required for fetching journal information")); } } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java index 89a6464d843f..06e7d15f30ce 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditor.java @@ -1,7 +1,5 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; - import javafx.scene.Parent; import javafx.scene.control.TextInputControl; import javafx.scene.layout.HBox; @@ -18,40 +16,55 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; +import javax.swing.undo.UndoManager; + 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) { - this.viewModel = new SimpleEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager); + 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() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java index acae084f6fc5..b93395e94b98 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/SimpleEditorViewModel.java @@ -1,16 +1,20 @@ package org.jabref.gui.fieldeditors; -import javax.swing.undo.UndoManager; - import org.jabref.gui.autocompleter.AppendWordsStrategy; import org.jabref.gui.autocompleter.AutoCompletionStrategy; import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.field.Field; +import javax.swing.undo.UndoManager; + 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); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java b/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java index c1a54103ac65..5eb84020d8ae 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java +++ b/src/main/java/org/jabref/gui/fieldeditors/URLUtil.java @@ -1,5 +1,8 @@ package org.jabref.gui.fieldeditors; +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.frame.ExternalApplicationsPreferences; + import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -8,17 +11,14 @@ import java.util.Objects; import java.util.Optional; -import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.frame.ExternalApplicationsPreferences; - public class URLUtil { private static final String URL_EXP = "^(https?|ftp)://.+"; // Detect Google search URL - private static final String GOOGLE_SEARCH_EXP = "^https?://(?:www\\.)?google\\.[\\.a-z]+?/url.*"; + private static final String GOOGLE_SEARCH_EXP = + "^https?://(?:www\\.)?google\\.[\\.a-z]+?/url.*"; - private URLUtil() { - } + private URLUtil() {} /** * Cleans URLs returned by Google search. @@ -92,7 +92,8 @@ public static boolean isURL(String url) { * @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: diff --git a/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java b/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java index 0951f3921d26..a97777ab8d86 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/UrlEditor.java @@ -1,9 +1,8 @@ package org.jabref.gui.fieldeditors; -import java.util.List; -import java.util.function.Supplier; +import com.airhacks.afterburner.views.ViewLoader; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -24,8 +23,10 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.util.List; +import java.util.function.Supplier; + +import javax.swing.undo.UndoManager; public class UrlEditor extends HBox implements FieldEditorFX { @@ -37,26 +38,40 @@ public class UrlEditor extends HBox implements FieldEditorFX { @Inject private KeyBindingRepository keyBindingRepository; @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(textArea, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); + 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( + textArea, viewModel.textProperty(), keyBindingRepository, undoAction, redoAction); Supplier> contextMenuSupplier = EditorMenus.getCleanupUrlMenu(textArea); textArea.initContextMenu(contextMenuSupplier, preferences.getKeyBindingRepository()); // init paste handler for UrlEditor to format pasted url link in textArea - textArea.setPasteActionHandler(() -> textArea.setText(new CleanupUrlFormatter().format(new TrimWhitespaceFormatter().format(textArea.getText())))); - - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); + textArea.setPasteActionHandler( + () -> + textArea.setText( + new CleanupUrlFormatter() + .format( + new TrimWhitespaceFormatter() + .format(textArea.getText())))); + + new EditorValidator(preferences) + .configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); } public UrlEditorViewModel getViewModel() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java index dceea6a52653..50074fd4e11c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/UrlEditorViewModel.java @@ -1,8 +1,6 @@ package org.jabref.gui.fieldeditors; -import java.io.IOException; - -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -16,25 +14,28 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; -import com.tobiasdiez.easybind.EasyBind; +import java.io.IOException; + +import javax.swing.undo.UndoManager; 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)) - ); + EasyBind.map(text, input -> StringUtil.isBlank(input) || !URLUtil.isURL(input))); } public boolean isValidUrlIsNotPresent() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java b/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java index 36af99d6c5cf..6413d1303a33 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java +++ b/src/main/java/org/jabref/gui/fieldeditors/WriteMetadataToSinglePdfAction.java @@ -1,11 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import javax.xml.transform.TransformerException; - import org.jabref.gui.DialogService; import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.FilePreferences; @@ -21,16 +15,22 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.LinkedFile; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import javax.xml.transform.TransformerException; + /** * Writes XMP metadata to the selected file according to the linking entry */ public class WriteMetadataToSinglePdfAction extends SimpleCommand { - private static final Logger LOGGER = LoggerFactory.getLogger(WriteMetadataToSinglePdfAction.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(WriteMetadataToSinglePdfAction.class); private final LinkedFile linkedFile; private final BibEntry entry; @@ -43,16 +43,17 @@ public class WriteMetadataToSinglePdfAction extends SimpleCommand { 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; @@ -67,46 +68,64 @@ public WriteMetadataToSinglePdfAction(LinkedFile linkedFile, @Override 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 { - try { - 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.")); - LOGGER.error("Error while writing metadata to {}", file.map(Path::toString).orElse(""), ex); - } - } - return null; - }); - writeTask - .onRunning(() -> setExecutable(false)) - .onFinished(() -> setExecutable(true)); + 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 { + try { + 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.")); + LOGGER.error( + "Error while writing metadata to {}", + file.map(Path::toString).orElse(""), + ex); + } + } + return null; + }); + 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 Exception { - // 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 Exception { + // 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); + EmbeddedBibFilePdfExporter embeddedBibExporter = + new EmbeddedBibFilePdfExporter( + databaseContext.getMode(), bibEntryTypesManager, fieldPreferences); embeddedBibExporter.exportToFileByPath( - databaseContext, - filePreferences, - file, - abbreviationRepository); + databaseContext, filePreferences, file, abbreviationRepository); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java index c5f95b72fa89..7e67e289a78c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/DefaultMenu.java @@ -1,8 +1,6 @@ package org.jabref.gui.fieldeditors.contextmenu; -import java.util.List; -import java.util.Objects; -import java.util.function.Supplier; +import com.tobiasdiez.easybind.EasyBind; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; @@ -14,7 +12,9 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.strings.StringUtil; -import com.tobiasdiez.easybind.EasyBind; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; public class DefaultMenu implements Supplier> { @@ -45,9 +45,16 @@ 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 +66,14 @@ 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); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java index 4aee5fa948a7..d08d2df9accf 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorContextAction.java @@ -1,6 +1,6 @@ package org.jabref.gui.fieldeditors.contextmenu; -import java.util.List; +import com.sun.javafx.scene.control.Properties; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -16,7 +16,7 @@ import org.jabref.gui.util.BindingsHelper; import org.jabref.logic.os.OS; -import com.sun.javafx.scene.control.Properties; +import java.util.List; public class EditorContextAction extends SimpleCommand { @@ -30,17 +30,37 @@ 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()); + 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 COPY -> + editableBinding.and(maskTextBinding.not()).and(hasSelectionBinding); case CUT -> maskTextBinding.not().and(hasSelectionBinding); case PASTE -> editableBinding.and(hasStringInClipboardBinding); case DELETE -> editableBinding.and(hasSelectionBinding); @@ -77,17 +97,27 @@ public void execute() { public static List getDefaultContextMenuItems(TextInputControl textInputControl) { ActionFactory factory = new ActionFactory(); - MenuItem selectAllMenuItem = factory.createMenuItem(StandardActions.SELECT_ALL, - new EditorContextAction(StandardActions.SELECT_ALL, textInputControl)); + MenuItem selectAllMenuItem = + factory.createMenuItem( + StandardActions.SELECT_ALL, + new EditorContextAction(StandardActions.SELECT_ALL, textInputControl)); if (SHOW_HANDLES) { selectAllMenuItem.getProperties().put("refreshMenu", Boolean.TRUE); } 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/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java index 4c02920b76e9..26e9a18c6109 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/EditorMenus.java @@ -1,8 +1,7 @@ package org.jabref.gui.fieldeditors.contextmenu; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; +import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; @@ -19,8 +18,9 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.strings.StringUtil; -import com.airhacks.afterburner.injection.Injector; -import com.tobiasdiez.easybind.EasyBind; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; /** * Provides context menus for the text fields of the entry editor. Note that we use {@link Supplier} to prevent an early @@ -38,9 +38,15 @@ public class EditorMenus { */ 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()))); + 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()))); List menuItems = new ArrayList<>(6); menuItems.add(normalizeNames); menuItems.addAll(new DefaultMenu(textInput).get()); @@ -54,12 +60,28 @@ public static Supplier> getNameMenu(final TextInputControl textIn * @param textArea text-area that this menu will be connected to * @return menu containing items of the default menu and an item for copying a DOI/DOI URL */ - public static Supplier> getDOIMenu(TextArea textArea, DialogService dialogService) { + public static Supplier> getDOIMenu( + TextArea textArea, DialogService dialogService) { return () -> { ActionFactory factory = new ActionFactory(); - ClipBoardManager clipBoardManager = Injector.instantiateModelOrService(ClipBoardManager.class); - MenuItem copyDoiMenuItem = factory.createMenuItem(StandardActions.COPY_DOI, new CopyDoiUrlAction(textArea, StandardActions.COPY_DOI, dialogService, clipBoardManager)); - MenuItem copyDoiUrlMenuItem = factory.createMenuItem(StandardActions.COPY_DOI_URL, new CopyDoiUrlAction(textArea, StandardActions.COPY_DOI_URL, dialogService, clipBoardManager)); + ClipBoardManager clipBoardManager = + Injector.instantiateModelOrService(ClipBoardManager.class); + MenuItem copyDoiMenuItem = + factory.createMenuItem( + StandardActions.COPY_DOI, + new CopyDoiUrlAction( + textArea, + StandardActions.COPY_DOI, + dialogService, + clipBoardManager)); + MenuItem copyDoiUrlMenuItem = + factory.createMenuItem( + StandardActions.COPY_DOI_URL, + new CopyDoiUrlAction( + textArea, + StandardActions.COPY_DOI_URL, + dialogService, + clipBoardManager)); List menuItems = new ArrayList<>(); menuItems.add(copyDoiMenuItem); menuItems.add(copyDoiUrlMenuItem); @@ -79,7 +101,9 @@ public static Supplier> getCleanupUrlMenu(TextArea textArea) { return () -> { MenuItem cleanupURL = new MenuItem(Localization.lang("Cleanup URL link")); cleanupURL.setDisable(textArea.textProperty().isEmpty().get()); - cleanupURL.setOnAction(event -> textArea.setText(new CleanupUrlFormatter().format(textArea.getText()))); + cleanupURL.setOnAction( + event -> + textArea.setText(new CleanupUrlFormatter().format(textArea.getText()))); List menuItems = new ArrayList<>(); menuItems.add(cleanupURL); return menuItems; diff --git a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java index 6fd3b673019c..c458b7505ba7 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java +++ b/src/main/java/org/jabref/gui/fieldeditors/contextmenu/ProtectedTermsMenu.java @@ -1,7 +1,6 @@ package org.jabref.gui.fieldeditors.contextmenu; -import java.util.Objects; -import java.util.Optional; +import com.airhacks.afterburner.injection.Injector; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; @@ -20,7 +19,8 @@ import org.jabref.logic.protectedterms.ProtectedTermsList; import org.jabref.logic.protectedterms.ProtectedTermsLoader; -import com.airhacks.afterburner.injection.Injector; +import java.util.Objects; +import java.util.Optional; class ProtectedTermsMenu extends Menu { @@ -28,34 +28,36 @@ class ProtectedTermsMenu extends Menu { private final TextInputControl textInputControl; private final ActionFactory factory = new ActionFactory(); - private final Action protectSelectionActionInformation = new Action() { - @Override - public String getText() { - return Localization.lang("Protect selection"); - } + private final Action protectSelectionActionInformation = + new Action() { + @Override + public String getText() { + return Localization.lang("Protect selection"); + } - @Override - public Optional getIcon() { - return Optional.of(IconTheme.JabRefIcons.PROTECT_STRING); - } + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.PROTECT_STRING); + } - @Override - public String getDescription() { - return Localization.lang("Add {} around selected text"); - } - }; + @Override + public String getDescription() { + return Localization.lang("Add {} around selected text"); + } + }; - private final Action unprotectSelectionActionInformation = new Action() { - @Override - public String getText() { - return Localization.lang("Unprotect selection"); - } + private final Action unprotectSelectionActionInformation = + new Action() { + @Override + public String getText() { + return Localization.lang("Unprotect selection"); + } - @Override - public String getDescription() { - return Localization.lang("Remove all {} in selected text"); - } - }; + @Override + public String getDescription() { + return Localization.lang("Remove all {} in selected text"); + } + }; private class ProtectSelectionAction extends SimpleCommand { ProtectSelectionAction() { @@ -67,7 +69,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 = " {"; } @@ -139,22 +142,38 @@ public void execute() { public ProtectedTermsMenu(final TextInputControl textInputControl) { super(Localization.lang("Protect terms")); this.textInputControl = textInputControl; - FORMATTER = new ProtectTermsFormatter(Injector.instantiateModelOrService(ProtectedTermsLoader.class)); - - getItems().addAll(factory.createMenuItem(protectSelectionActionInformation, new ProtectSelectionAction()), - getExternalFilesMenu(), - new SeparatorMenuItem(), - factory.createMenuItem(() -> Localization.lang("Format field"), new FormatFieldAction()), - factory.createMenuItem(unprotectSelectionActionInformation, new UnprotectSelectionAction())); + FORMATTER = + new ProtectTermsFormatter( + Injector.instantiateModelOrService(ProtectedTermsLoader.class)); + + getItems() + .addAll( + factory.createMenuItem( + protectSelectionActionInformation, new ProtectSelectionAction()), + getExternalFilesMenu(), + new SeparatorMenuItem(), + factory.createMenuItem( + () -> Localization.lang("Format field"), new FormatFieldAction()), + factory.createMenuItem( + unprotectSelectionActionInformation, + new UnprotectSelectionAction())); } private Menu getExternalFilesMenu() { - Menu protectedTermsMenu = factory.createSubMenu(() -> Localization.lang("Add selected text to list")); - ProtectedTermsLoader loader = Injector.instantiateModelOrService(ProtectedTermsLoader.class); + 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)))); + .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")); diff --git a/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java index 5367ebfa4295..6be8b1c02c8b 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/identifier/BaseIdentifierEditorViewModel.java @@ -1,9 +1,6 @@ package org.jabref.gui.fieldeditors.identifier; -import java.io.IOException; -import java.util.Optional; - -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; @@ -25,30 +22,38 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.identifier.Identifier; - -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class BaseIdentifierEditorViewModel extends AbstractEditorViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(BaseIdentifierEditorViewModel.class); +import java.io.IOException; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + +public abstract class BaseIdentifierEditorViewModel + extends AbstractEditorViewModel { + private static final Logger LOGGER = + LoggerFactory.getLogger(BaseIdentifierEditorViewModel.class); protected BooleanProperty isInvalidIdentifier = new SimpleBooleanProperty(); protected final BooleanProperty identifierLookupInProgress = new SimpleBooleanProperty(false); protected final BooleanProperty canLookupIdentifier = new SimpleBooleanProperty(true); - protected final BooleanProperty canFetchBibliographyInformationById = new SimpleBooleanProperty(); + protected final BooleanProperty canFetchBibliographyInformationById = + new SimpleBooleanProperty(); protected IdentifierParser identifierParser; - protected final ObjectProperty> identifier = new SimpleObjectProperty<>(Optional.empty()); + 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; @@ -62,7 +67,8 @@ public BaseIdentifierEditorViewModel(Field field, *

    * NOTE: This method MUST be called by all the implementation view models in their principal constructor * */ - protected final void configure(boolean canFetchBibliographyInformationById, boolean canLookupIdentifier) { + protected final void configure( + boolean canFetchBibliographyInformationById, boolean canLookupIdentifier) { this.canLookupIdentifier.set(canLookupIdentifier); this.canFetchBibliographyInformationById.set(canFetchBibliographyInformationById); } @@ -79,13 +85,21 @@ 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")); + 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")); + 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())); + 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.showWarningDialogAndWait( + Localization.lang("Look up %0", fetcher.getName()), + Localization.lang("Error occurred %0", exception.getCause().getMessage())); } } @@ -122,7 +136,9 @@ public BooleanProperty identifierLookupInProgressProperty() { } public void fetchBibliographyInformation(BibEntry bibEntry) { - LOGGER.warn("Unable to fetch bibliography information using the '{}' identifier", field.getDisplayName()); + LOGGER.warn( + "Unable to fetch bibliography information using the '{}' identifier", + field.getDisplayName()); } public void lookupIdentifier(BibEntry bibEntry) { @@ -130,14 +146,19 @@ 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); - } - } - ); + 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); + } + }); } @Override @@ -145,6 +166,7 @@ public void bindToEntry(BibEntry entry) { super.bindToEntry(entry); identifierParser = new IdentifierParser(entry); EasyBind.subscribe(textProperty(), ignored -> updateIdentifier()); - EasyBind.subscribe(identifier, newIdentifier -> isInvalidIdentifier.set(newIdentifier.isEmpty())); + EasyBind.subscribe( + identifier, newIdentifier -> isInvalidIdentifier.set(newIdentifier.isEmpty())); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java index 7b86f1f2966e..3461a22b4377 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/identifier/DoiIdentifierEditorViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.fieldeditors.identifier; -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.autocompleter.SuggestionProvider; @@ -16,24 +14,33 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.DOI; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.swing.undo.UndoManager; + public class DoiIdentifierEditorViewModel extends BaseIdentifierEditorViewModel { public static final Logger LOGGER = LoggerFactory.getLogger(DoiIdentifierEditorViewModel.class); private final UndoManager undoManager; private final StateManager stateManager; - 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; configure(true, true); @@ -44,29 +51,42 @@ public void lookupIdentifier(BibEntry bibEntry) { CrossRef doiFetcher = new CrossRef(); BackgroundTask.wrap(() -> doiFetcher.findIdentifier(entry)) - .onRunning(() -> identifierLookupInProgress.setValue(true)) - .onFinished(() -> identifierLookupInProgress.setValue(false)) - .onSuccess(identifier -> { - if (identifier.isPresent()) { - entry.setField(field, identifier.get().getNormalized()); - } else { - dialogService.notify(Localization.lang("No %0 found", field.getDisplayName())); - } - }).onFailure(e -> handleIdentifierFetchingError(e, doiFetcher)).executeWith(taskExecutor); + .onRunning(() -> identifierLookupInProgress.setValue(true)) + .onFinished(() -> identifierLookupInProgress.setValue(false)) + .onSuccess( + identifier -> { + if (identifier.isPresent()) { + entry.setField(field, identifier.get().getNormalized()); + } else { + dialogService.notify( + Localization.lang("No %0 found", field.getDisplayName())); + } + }) + .onFailure(e -> handleIdentifierFetchingError(e, doiFetcher)) + .executeWith(taskExecutor); } @Override public void fetchBibliographyInformation(BibEntry bibEntry) { - stateManager.getActiveDatabase().ifPresentOrElse( - databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, undoManager) - .fetchAndMerge(entry, field), - () -> dialogService.notify(Localization.lang("No library selected")) - ); + stateManager + .getActiveDatabase() + .ifPresentOrElse( + databaseContext -> + new FetchAndMergeEntry( + databaseContext, + taskExecutor, + preferences, + dialogService, + undoManager) + .fetchAndMerge(entry, field), + () -> dialogService.notify(Localization.lang("No library selected"))); } @Override public void openExternalLink() { - identifier.get().map(DOI::getDOI) - .ifPresent(s -> NativeDesktop.openCustomDoi(s, preferences, dialogService)); + identifier + .get() + .map(DOI::getDOI) + .ifPresent(s -> NativeDesktop.openCustomDoi(s, preferences, dialogService)); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java index b567fa279190..e6e6fa209fa3 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/identifier/EprintIdentifierEditorViewModel.java @@ -1,6 +1,6 @@ package org.jabref.gui.fieldeditors.identifier; -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; import javafx.collections.MapChangeListener; import javafx.collections.WeakMapChangeListener; @@ -17,46 +17,64 @@ import org.jabref.model.entry.identifier.ArXivIdentifier; import org.jabref.model.entry.identifier.EprintIdentifier; -import com.tobiasdiez.easybind.EasyBind; +import javax.swing.undo.UndoManager; -public class EprintIdentifierEditorViewModel extends BaseIdentifierEditorViewModel { +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 -> { - Field changedField = change.getKey(); - if (StandardField.EPRINTTYPE == changedField) { - updateIdentifier(); - } - }; - - 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); - EasyBind.subscribe(identifier, newIdentifier -> { - newIdentifier.ifPresent(id -> { - // TODO: We already have a common superclass between ArXivIdentifier and ARK. This could be refactored further. - if (id instanceof ArXivIdentifier) { - configure(true, false); - } else if (id instanceof ARK) { - configure(false, false); + private MapChangeListener eprintTypeFieldListener = + change -> { + Field changedField = change.getKey(); + if (StandardField.EPRINTTYPE == changedField) { + updateIdentifier(); } - }); - }); + }; + + 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); + EasyBind.subscribe( + identifier, + newIdentifier -> { + newIdentifier.ifPresent( + id -> { + // TODO: We already have a common superclass between ArXivIdentifier + // and ARK. This could be refactored further. + if (id instanceof ArXivIdentifier) { + configure(true, false); + } else if (id instanceof ARK) { + configure(false, false); + } + }); + }); } @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 + // 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)); + entry.getFieldsObservable() + .addListener(new WeakMapChangeListener<>(eprintTypeFieldListener)); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java index 99daab73f22d..6782bb0c39b6 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/identifier/ISBNIdentifierEditorViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.fieldeditors.identifier; -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.autocompleter.SuggestionProvider; @@ -14,18 +12,28 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.identifier.ISBN; +import javax.swing.undo.UndoManager; + 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); @@ -33,10 +41,17 @@ public ISBNIdentifierEditorViewModel(SuggestionProvider suggestionProvider, @Override public void fetchBibliographyInformation(BibEntry bibEntry) { - stateManager.getActiveDatabase().ifPresentOrElse( - databaseContext -> new FetchAndMergeEntry(databaseContext, taskExecutor, preferences, dialogService, undoManager) - .fetchAndMerge(entry, field), - () -> dialogService.notify(Localization.lang("No library selected")) - ); + stateManager + .getActiveDatabase() + .ifPresentOrElse( + databaseContext -> + new FetchAndMergeEntry( + databaseContext, + taskExecutor, + preferences, + dialogService, + undoManager) + .fetchAndMerge(entry, field), + () -> dialogService.notify(Localization.lang("No library selected"))); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java b/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java index f0c308f91797..515c9a34cd86 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/identifier/IdentifierEditor.java @@ -1,8 +1,13 @@ package org.jabref.gui.fieldeditors.identifier; -import java.util.Optional; +import static org.jabref.model.entry.field.StandardField.DOI; +import static org.jabref.model.entry.field.StandardField.EPRINT; +import static org.jabref.model.entry.field.StandardField.ISBN; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.Parent; @@ -25,13 +30,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.injection.Injector; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.util.Optional; -import static org.jabref.model.entry.field.StandardField.DOI; -import static org.jabref.model.entry.field.StandardField.EPRINT; -import static org.jabref.model.entry.field.StandardField.ISBN; +import javax.swing.undo.UndoManager; public class IdentifierEditor extends HBox implements FieldEditorFX { @@ -48,9 +49,8 @@ public class IdentifierEditor extends HBox implements FieldEditorFX { 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. @@ -58,36 +58,65 @@ public IdentifierEditor(Field field, switch (field) { case DOI -> - this.viewModel = new DoiIdentifierEditorViewModel(suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager, stateManager); + this.viewModel = + new DoiIdentifierEditorViewModel( + suggestionProvider, + fieldCheckers, + dialogService, + taskExecutor, + preferences, + undoManager, + stateManager); case ISBN -> - this.viewModel = new ISBNIdentifierEditorViewModel(suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager, stateManager); + this.viewModel = + new ISBNIdentifierEditorViewModel( + suggestionProvider, + fieldCheckers, + dialogService, + taskExecutor, + preferences, + undoManager, + stateManager); case EPRINT -> - this.viewModel = new EprintIdentifierEditorViewModel(suggestionProvider, fieldCheckers, dialogService, taskExecutor, preferences, undoManager); + 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(); textArea.textProperty().bindBidirectional(viewModel.textProperty()); fetchInformationByIdentifierButton.setTooltip( - new Tooltip(Localization.lang("Get bibliographic data from %0", field.getDisplayName()))); + new Tooltip( + Localization.lang( + "Get bibliographic data from %0", field.getDisplayName()))); lookupIdentifierButton.setTooltip( new Tooltip(Localization.lang("Look up %0", field.getDisplayName()))); if (field.equals(DOI)) { - textArea.initContextMenu(EditorMenus.getDOIMenu(textArea, dialogService), preferences.getKeyBindingRepository()); + textArea.initContextMenu( + EditorMenus.getDOIMenu(textArea, dialogService), + preferences.getKeyBindingRepository()); } else { - textArea.initContextMenu(new DefaultMenu(textArea), preferences.getKeyBindingRepository()); + textArea.initContextMenu( + new DefaultMenu(textArea), preferences.getKeyBindingRepository()); } - new EditorValidator(preferences).configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); + new EditorValidator(preferences) + .configureValidation(viewModel.getFieldValidator().getValidationStatus(), textArea); } public BaseIdentifierEditorViewModel getViewModel() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java b/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java index 24712ff4a3b8..5e3a6ac6206d 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java +++ b/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoView.java @@ -1,6 +1,6 @@ package org.jabref.gui.fieldeditors.journalinfo; -import java.util.Objects; +import com.airhacks.afterburner.views.ViewLoader; import javafx.fxml.FXML; import javafx.scene.Node; @@ -11,7 +11,7 @@ import org.jabref.architecture.AllowedToUseClassGetResource; import org.jabref.logic.importer.FetcherException; -import com.airhacks.afterburner.views.ViewLoader; +import java.util.Objects; @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") public class JournalInfoView extends VBox { @@ -31,11 +31,12 @@ public class JournalInfoView extends VBox { public JournalInfoView() { this.viewModel = new JournalInfoViewModel(); - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); - this.getStylesheets().add(Objects.requireNonNull(JournalInfoView.class.getResource("JournalInfo.css")).toExternalForm()); + this.getStylesheets() + .add( + Objects.requireNonNull(JournalInfoView.class.getResource("JournalInfo.css")) + .toExternalForm()); title.textProperty().bind(viewModel.titleProperty()); categories.textProperty().bind(viewModel.categoriesProperty()); @@ -45,7 +46,8 @@ public JournalInfoView() { bindChartProperties(); } - public Node populateJournalInformation(String issn, String journalName) throws FetcherException { + public Node populateJournalInformation(String issn, String journalName) + throws FetcherException { viewModel.populateJournalInformation(issn, journalName); return this; } @@ -59,7 +61,8 @@ private void bindChartProperties() { citableDocsPrevious3YearsChart.setData(viewModel.getCitableDocsPrevious3YearsData()); citesOutgoingChart.setData(viewModel.getCitesOutgoingData()); citesOutgoingPerDocChart.setData(viewModel.getCitesOutgoingPerDocData()); - citesIncomingByRecentlyPublishedChart.setData(viewModel.getCitesIncomingByRecentlyPublishedData()); + citesIncomingByRecentlyPublishedChart.setData( + viewModel.getCitesIncomingByRecentlyPublishedData()); docsThisYearChart.setData(viewModel.getDocsThisYearData()); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java index 671bcc3024b5..ea713a3fc52c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/journalinfo/JournalInfoViewModel.java @@ -1,10 +1,5 @@ package org.jabref.gui.fieldeditors.journalinfo; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -16,6 +11,11 @@ import org.jabref.logic.importer.fetcher.JournalInformationFetcher; import org.jabref.logic.journals.JournalInformation; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + public class JournalInfoViewModel extends AbstractViewModel { private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(); private final ReadOnlyStringWrapper country = new ReadOnlyStringWrapper(); @@ -24,33 +24,46 @@ public class JournalInfoViewModel extends AbstractViewModel { 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> docsThisYearData = FXCollections.observableArrayList(); - - public void populateJournalInformation(String issn, String journalName) throws FetcherException { - Optional journalInformationOptional = new JournalInformationFetcher().getJournalInformation(issn, journalName); - - journalInformationOptional.ifPresent(journalInformation -> { - setTitle(journalInformation.title()); - setCountry(journalInformation.country()); - setCategories(getFormattedCategories(journalInformation)); - setPublisher(getFormattedPublisher(journalInformation)); - setScimagoId(journalInformation.scimagoId()); - sethIndex(journalInformation.hIndex()); - setIssn(journalInformation.issn()); - sjrData.add(convertToSeries(journalInformation.sjrArray())); - snipData.add(convertToSeries(journalInformation.snipArray())); - citableDocsPrevious3YearsData.add(convertToSeries(journalInformation.citableDocsPrevious3Years())); - citesOutgoingData.add(convertToSeries(journalInformation.citesOutgoing())); - citesOutgoingPerDocData.add(convertToSeries(journalInformation.citesOutgoingPerDoc())); - citesIncomingByRecentlyPublishedData.add(convertToSeries(journalInformation.citesIncomingByRecentlyPublished())); - docsThisYearData.add(convertToSeries(journalInformation.docsThisYear())); - }); + 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> docsThisYearData = + FXCollections.observableArrayList(); + + public void populateJournalInformation(String issn, String journalName) + throws FetcherException { + Optional journalInformationOptional = + new JournalInformationFetcher().getJournalInformation(issn, journalName); + + journalInformationOptional.ifPresent( + journalInformation -> { + setTitle(journalInformation.title()); + setCountry(journalInformation.country()); + setCategories(getFormattedCategories(journalInformation)); + setPublisher(getFormattedPublisher(journalInformation)); + setScimagoId(journalInformation.scimagoId()); + sethIndex(journalInformation.hIndex()); + setIssn(journalInformation.issn()); + sjrData.add(convertToSeries(journalInformation.sjrArray())); + snipData.add(convertToSeries(journalInformation.snipArray())); + citableDocsPrevious3YearsData.add( + convertToSeries(journalInformation.citableDocsPrevious3Years())); + citesOutgoingData.add(convertToSeries(journalInformation.citesOutgoing())); + citesOutgoingPerDocData.add( + convertToSeries(journalInformation.citesOutgoingPerDoc())); + citesIncomingByRecentlyPublishedData.add( + convertToSeries(journalInformation.citesIncomingByRecentlyPublished())); + docsThisYearData.add(convertToSeries(journalInformation.docsThisYear())); + }); } public String getTitle() { @@ -157,7 +170,8 @@ public ObservableList> getCitesOutgoingPerDocData return citesOutgoingPerDocData; } - public ObservableList> getCitesIncomingByRecentlyPublishedData() { + public ObservableList> + getCitesIncomingByRecentlyPublishedData() { return citesIncomingByRecentlyPublishedData; } @@ -168,15 +182,15 @@ public ObservableList> getDocsThisYearData() { public XYChart.Series convertToSeries(List> data) { XYChart.Series series = new XYChart.Series<>(); data.stream() - .map(pair -> new XYChart.Data<>(pair.getKey().toString(), pair.getValue())) - .forEach(series.getData()::add); + .map(pair -> new XYChart.Data<>(pair.getKey().toString(), pair.getValue())) + .forEach(series.getData()::add); return series; } 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) { diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java index 01fc59e37192..f6a4d48ffb11 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/MonthEditorViewModel.java @@ -1,10 +1,5 @@ package org.jabref.gui.fieldeditors.optioneditors; -import java.util.Arrays; -import java.util.Collection; - -import javax.swing.undo.UndoManager; - import javafx.util.StringConverter; import org.jabref.gui.autocompleter.SuggestionProvider; @@ -14,10 +9,20 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; +import java.util.Arrays; +import java.util.Collection; + +import javax.swing.undo.UndoManager; + public class MonthEditorViewModel extends OptionEditorViewModel { private 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; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java index 2cf488335045..a8bdb5d4b444 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditor.java @@ -1,5 +1,7 @@ package org.jabref.gui.fieldeditors.optioneditors; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.Parent; import javafx.scene.control.ComboBox; @@ -11,8 +13,6 @@ import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.model.entry.BibEntry; -import com.airhacks.afterburner.views.ViewLoader; - /** * Field editor that provides various pre-defined options as a drop-down combobox. */ @@ -22,22 +22,27 @@ public class OptionEditor extends HBox implements FieldEditorFX { @FXML private ComboBox comboBox; public OptionEditor(OptionEditorViewModel viewModel) { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); this.viewModel = viewModel; comboBox.setConverter(viewModel.getStringConverter()); - comboBox.setCellFactory(new ViewModelListCellFactory().withText(viewModel::convertToDisplayText)); + comboBox.setCellFactory( + new ViewModelListCellFactory().withText(viewModel::convertToDisplayText)); comboBox.getItems().setAll(viewModel.getItems()); comboBox.getEditor().textProperty().bindBidirectional(viewModel.textProperty()); - comboBox.getEditor().setOnContextMenuRequested(event -> { - ContextMenu contextMenu = new ContextMenu(); - contextMenu.getItems().setAll(EditorContextAction.getDefaultContextMenuItems(comboBox.getEditor())); - contextMenu.show(comboBox, event.getScreenX(), event.getScreenY()); - }); + comboBox.getEditor() + .setOnContextMenuRequested( + event -> { + ContextMenu contextMenu = new ContextMenu(); + contextMenu + .getItems() + .setAll( + EditorContextAction.getDefaultContextMenuItems( + comboBox.getEditor())); + contextMenu.show(comboBox, event.getScreenX(), event.getScreenY()); + }); } public OptionEditorViewModel getViewModel() { diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java index e365781cfb51..3505d6a0df98 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/OptionEditorViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.fieldeditors.optioneditors; -import java.util.Collection; - -import javax.swing.undo.UndoManager; - import javafx.util.StringConverter; import org.jabref.gui.autocompleter.SuggestionProvider; @@ -11,9 +7,17 @@ import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.field.Field; +import java.util.Collection; + +import javax.swing.undo.UndoManager; + 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); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java index 09524b2e23d8..b266982049d2 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/CustomFieldEditorViewModel.java @@ -1,19 +1,23 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; +import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.logic.integrity.FieldCheckers; +import org.jabref.model.entry.field.Field; + import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.undo.UndoManager; -import org.jabref.gui.autocompleter.SuggestionProvider; -import org.jabref.logic.integrity.FieldCheckers; -import org.jabref.model.entry.field.Field; - public class CustomFieldEditorViewModel extends StringMapBasedEditorViewModel { - public CustomFieldEditorViewModel(Field field, SuggestionProvider suggestionProvider, - FieldCheckers fieldCheckers, UndoManager undoManager, List selectorValues) { + public CustomFieldEditorViewModel( + Field field, + SuggestionProvider suggestionProvider, + FieldCheckers fieldCheckers, + UndoManager undoManager, + List selectorValues) { super(field, suggestionProvider, fieldCheckers, undoManager, getMap(selectorValues)); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java index 960572f0079d..062725c38c7b 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/EditorTypeEditorViewModel.java @@ -1,24 +1,33 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.Map; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; +import java.util.Map; + +import javax.swing.undo.UndoManager; + 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/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java index 496b949bca44..0eeb613b353a 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/GenderEditorViewModel.java @@ -1,24 +1,33 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.Map; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; +import java.util.Map; + +import javax.swing.undo.UndoManager; + 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/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java index 95a00ac2180a..40670deff3b9 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/MapBasedEditorViewModel.java @@ -1,8 +1,6 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.Collection; - -import javax.swing.undo.UndoManager; +import com.google.common.collect.BiMap; import javafx.util.StringConverter; @@ -10,11 +8,13 @@ import org.jabref.gui.fieldeditors.optioneditors.OptionEditorViewModel; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.field.Field; - -import com.google.common.collect.BiMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collection; + +import javax.swing.undo.UndoManager; + /** * View model for a field editor that shows various options backed by a map. */ @@ -22,7 +22,11 @@ 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); } @@ -63,7 +67,10 @@ protected T getValueFromString(String string) { try { return (T) string; } catch (ClassCastException ex) { - LOGGER.error("Could not cast string to type %1$s. Try overriding the method in a subclass and provide a conversion from string to the concrete type %1$s".formatted(string.getClass()), ex); + LOGGER.error( + "Could not cast string to type %1$s. Try overriding the method in a subclass and provide a conversion from string to the concrete type %1$s" + .formatted(string.getClass()), + ex); } return null; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java index 81864bbe575d..463e5c8f3d6d 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PaginationEditorViewModel.java @@ -1,24 +1,33 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.Map; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; +import java.util.Map; + +import javax.swing.undo.UndoManager; + 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/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java index c602e1db665e..20d6ffb528aa 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/PatentTypeEditorViewModel.java @@ -1,18 +1,22 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.undo.UndoManager; + 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()); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java index 9a6d6a7ae5eb..8ddfcfefbca0 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/StringMapBasedEditorViewModel.java @@ -1,21 +1,26 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.Map; - -import javax.swing.undo.UndoManager; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.field.Field; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; +import java.util.Map; + +import javax.swing.undo.UndoManager; public abstract class StringMapBasedEditorViewModel extends MapBasedEditorViewModel { 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); diff --git a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java index 5b9b75f12433..d75f711a240c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/TypeEditorViewModel.java @@ -1,26 +1,35 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.Map; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; +import java.util.Map; + +import javax.swing.undo.UndoManager; + 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/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java index 856f5372a799..664966b428d7 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/optioneditors/mapbased/YesNoEditorViewModel.java @@ -1,24 +1,32 @@ package org.jabref.gui.fieldeditors.optioneditors.mapbased; -import java.util.Map; - -import javax.swing.undo.UndoManager; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.entry.field.Field; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; +import java.util.Map; + +import javax.swing.undo.UndoManager; 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/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java b/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java index b2d7587afb1a..693d7eae0c01 100644 --- a/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java +++ b/src/main/java/org/jabref/gui/frame/ExternalApplicationsPreferences.java @@ -1,9 +1,5 @@ package org.jabref.gui.frame; -import java.util.Comparator; -import java.util.Set; -import java.util.TreeSet; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -16,6 +12,10 @@ import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.logic.push.CitationCommandString; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; + public class ExternalApplicationsPreferences { private final StringProperty eMailSubject; @@ -29,21 +29,25 @@ public class ExternalApplicationsPreferences { 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, - CitationCommandString citeCommand, - CitationCommandString defaultCiteCommand, - 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, + CitationCommandString citeCommand, + CitationCommandString defaultCiteCommand, + Set externalFileTypes, + boolean useCustomTerminal, + String customTerminalCommand, + boolean useCustomFileBrowser, + String customFileBrowserCommand, + String kindleEmail) { this.eMailSubject = new SimpleStringProperty(eMailSubject); - this.shouldAutoOpenEmailAttachmentsFolder = new SimpleBooleanProperty(shouldAutoOpenEmailAttachmentsFolder); + this.shouldAutoOpenEmailAttachmentsFolder = + new SimpleBooleanProperty(shouldAutoOpenEmailAttachmentsFolder); this.citeCommand = new SimpleObjectProperty<>(citeCommand); this.defaultCiteCommand = new SimpleObjectProperty<>(defaultCiteCommand); this.externalFileTypes.addAll(externalFileTypes); diff --git a/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java b/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java index 14124c83d554..740180363c53 100644 --- a/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java +++ b/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java @@ -1,8 +1,5 @@ package org.jabref.gui.frame; -import java.nio.file.Files; -import java.nio.file.Path; - import javafx.beans.InvalidationListener; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; @@ -14,6 +11,9 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileHistory; +import java.nio.file.Files; +import java.nio.file.Path; + public class FileHistoryMenu extends Menu { protected final MenuItem clearRecentLibraries; @@ -21,7 +21,10 @@ public class FileHistoryMenu extends Menu { 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(); @@ -73,20 +76,20 @@ 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. + // 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); diff --git a/src/main/java/org/jabref/gui/frame/FrameDndHandler.java b/src/main/java/org/jabref/gui/frame/FrameDndHandler.java index 424b35124884..b6a3f414031f 100644 --- a/src/main/java/org/jabref/gui/frame/FrameDndHandler.java +++ b/src/main/java/org/jabref/gui/frame/FrameDndHandler.java @@ -1,11 +1,6 @@ package org.jabref.gui.frame; -import java.io.File; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Collectors; +import com.tobiasdiez.easybind.EasyBind; import javafx.scene.Node; import javafx.scene.Scene; @@ -24,11 +19,16 @@ import org.jabref.logic.util.io.FileUtil; import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.GroupTreeNode; - -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + public class FrameDndHandler { private static final Logger LOGGER = LoggerFactory.getLogger(FrameDndHandler.class); @@ -37,10 +37,11 @@ public class FrameDndHandler { 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; @@ -53,28 +54,46 @@ void initDragAndDrop() { Tab dndIndicator = new Tab(Localization.lang("Open files..."), null); dndIndicator.getStyleClass().add("drop"); - EasyBind.subscribe(tabPane.skinProperty(), skin -> { - if (!(skin instanceof TabPaneSkin)) { - return; - } - // Add drag and drop listeners to JabRefFrame - 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 - 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)); - } - event.consume(); - }); - scene.get().setOnDragExited(event -> tabPane.getTabs().remove(dndIndicator)); - scene.get().setOnDragDropped(event -> onSceneDragDropped(event, dndIndicator)); - }); + EasyBind.subscribe( + tabPane.skinProperty(), + skin -> { + if (!(skin instanceof TabPaneSkin)) { + return; + } + // Add drag and drop listeners to JabRefFrame + 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 + 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)); + } + event.consume(); + }); + scene.get().setOnDragExited(event -> tabPane.getTabs().remove(dndIndicator)); + scene.get().setOnDragDropped(event -> onSceneDragDropped(event, dndIndicator)); + }); } - private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, Tab dndIndicator) { + private void onTabDragDropped( + Node destinationTabNode, DragEvent tabDragEvent, Tab dndIndicator) { Dragboard dragboard = tabDragEvent.getDragboard(); if (hasBibFiles(dragboard)) { @@ -92,8 +111,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; } @@ -105,9 +124,10 @@ private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, T } if (hasEntries(dragboard)) { - List entryCopies = stateManager.getLocalDragboard().getBibEntries() - .stream().map(entry -> (BibEntry) entry.clone()) - .toList(); + List entryCopies = + stateManager.getLocalDragboard().getBibEntries().stream() + .map(entry -> (BibEntry) entry.clone()) + .toList(); destinationLibraryTab.dropEntry(entryCopies); } else if (hasGroups(dragboard)) { dropGroups(dragboard, destinationLibraryTab); @@ -122,21 +142,16 @@ 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(); - copyGroupTreeNode(destinationLibraryTab, destinationLibraryGroupRoot, groupTreeNodeToCopy); + GroupTreeNode groupTreeNodeToCopy = groupsTreeNode.getChildByPath(pathToSource).get(); + copyGroupTreeNode( + destinationLibraryTab, destinationLibraryGroupRoot, groupTreeNodeToCopy); } } @@ -189,30 +204,32 @@ private void onSceneDragDropped(DragEvent event, Tab dndIndicator) { private void copyRootNode(LibraryTab destinationLibraryTab) { if (destinationLibraryTab.getBibDatabaseContext().getMetaData().getGroups().isPresent() - && stateManager.getActiveDatabase().isEmpty()) { + && stateManager.getActiveDatabase().isEmpty()) { return; } // 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)); @@ -235,7 +252,10 @@ private List getBibFiles(Dragboard dragboard) { if (!dragboard.hasFiles()) { return Collections.emptyList(); } else { - return dragboard.getFiles().stream().map(File::toPath).filter(FileUtil::isBibFile).collect(Collectors.toList()); + return dragboard.getFiles().stream() + .map(File::toPath) + .filter(FileUtil::isBibFile) + .collect(Collectors.toList()); } } diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/src/main/java/org/jabref/gui/frame/JabRefFrame.java index d38d511fe58d..97ee79081a75 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -1,12 +1,9 @@ package org.jabref.gui.frame; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Supplier; +import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyObservableList; +import com.tobiasdiez.easybind.Subscription; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -24,6 +21,7 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; +import org.fxmisc.richtext.CodeArea; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -55,16 +53,18 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.injection.Injector; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.EasyObservableList; -import com.tobiasdiez.easybind.Subscription; -import org.fxmisc.richtext.CodeArea; import org.jspecify.annotations.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; + /** * Represents the inner frame of the JabRef window */ @@ -83,7 +83,8 @@ public class JabRefFrame extends BorderPane implements LibraryTabContainer, UiMe private final FileHistoryMenu fileHistory; private final FrameDndHandler frameDndHandler; - @SuppressWarnings({"FieldCanBeLocal"}) private EasyObservableList openDatabaseList; + @SuppressWarnings({"FieldCanBeLocal"}) + private EasyObservableList openDatabaseList; private final Stage mainStage; private final StateManager stateManager; @@ -101,17 +102,18 @@ public class JabRefFrame extends BorderPane implements LibraryTabContainer, UiMe private Subscription dividerSubscription; - public JabRefFrame(Stage mainStage, - DialogService dialogService, - FileUpdateMonitor fileUpdateMonitor, - GuiPreferences preferences, - AiService aiService, - ChatHistoryService chatHistoryService, - StateManager stateManager, - CountingUndoManager undoManager, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public JabRefFrame( + Stage mainStage, + DialogService dialogService, + FileUpdateMonitor fileUpdateMonitor, + GuiPreferences preferences, + AiService aiService, + ChatHistoryService chatHistoryService, + StateManager stateManager, + CountingUndoManager undoManager, + BibEntryTypesManager entryTypesManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { this.mainStage = mainStage; this.dialogService = dialogService; this.fileUpdateMonitor = fileUpdateMonitor; @@ -127,63 +129,64 @@ public JabRefFrame(Stage mainStage, setId("frame"); // Create components - this.viewModel = new JabRefFrameViewModel( - preferences, - aiService, - stateManager, - dialogService, - this, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipBoardManager, - taskExecutor); + this.viewModel = + new JabRefFrameViewModel( + preferences, + aiService, + stateManager, + dialogService, + this, + entryTypesManager, + fileUpdateMonitor, + undoManager, + clipBoardManager, + taskExecutor); Injector.setModelOrService(UiMessageHandler.class, viewModel); - this.frameDndHandler = new FrameDndHandler( - tabbedPane, - mainStage::getScene, - this::getOpenDatabaseAction, - stateManager); - - this.globalSearchBar = new GlobalSearchBar( - this, - stateManager, - this.preferences, - undoManager, - dialogService, - SearchType.NORMAL_SEARCH); - - this.sidePane = new SidePane( - this, - this.preferences, - chatHistoryService, - Injector.instantiateModelOrService(JournalAbbreviationRepository.class), - taskExecutor, - dialogService, - stateManager, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, - undoManager); - - this.pushToApplicationCommand = new PushToApplicationCommand( - stateManager, - dialogService, - this.preferences, - taskExecutor); - - this.fileHistory = new FileHistoryMenu( - this.preferences.getLastFilesOpenedPreferences().getFileHistory(), - dialogService, - getOpenDatabaseAction()); - this.setOnKeyTyped(key -> { - if (this.fileHistory.isShowing()) { - if (this.fileHistory.openFileByKey(key)) { - this.fileHistory.getParentMenu().hide(); - } - } - }); + this.frameDndHandler = + new FrameDndHandler( + tabbedPane, mainStage::getScene, this::getOpenDatabaseAction, stateManager); + + this.globalSearchBar = + new GlobalSearchBar( + this, + stateManager, + this.preferences, + undoManager, + dialogService, + SearchType.NORMAL_SEARCH); + + this.sidePane = + new SidePane( + this, + this.preferences, + chatHistoryService, + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), + taskExecutor, + dialogService, + stateManager, + fileUpdateMonitor, + entryTypesManager, + clipBoardManager, + undoManager); + + this.pushToApplicationCommand = + new PushToApplicationCommand( + stateManager, dialogService, this.preferences, taskExecutor); + + this.fileHistory = + new FileHistoryMenu( + this.preferences.getLastFilesOpenedPreferences().getFileHistory(), + dialogService, + getOpenDatabaseAction()); + this.setOnKeyTyped( + key -> { + if (this.fileHistory.isShowing()) { + if (this.fileHistory.openFileByKey(key)) { + this.fileHistory.getParentMenu().hide(); + } + } + }); initLayout(); initKeyBindings(); @@ -192,36 +195,38 @@ public JabRefFrame(Stage mainStage, } private void initLayout() { - 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); + 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); VBox head = new VBox(mainMenu, mainToolBar); head.setSpacing(0d); @@ -251,79 +256,147 @@ private void updateSidePane() { public void updateDividerPosition() { if (mainStage.isShowing() && !sidePane.getChildren().isEmpty()) { - splitPane.setDividerPositions(preferences.getGuiPreferences().getSidePaneWidth() / splitPane.getWidth()); - dividerSubscription = EasyBind.listen(sidePane.widthProperty(), (obs, old, newVal) -> preferences.getGuiPreferences().setSidePaneWidth(newVal.doubleValue())); + splitPane.setDividerPositions( + preferences.getGuiPreferences().getSidePaneWidth() / splitPane.getWidth()); + dividerSubscription = + EasyBind.listen( + sidePane.widthProperty(), + (obs, old, newVal) -> + preferences + .getGuiPreferences() + .setSidePaneWidth(newVal.doubleValue())); } } private void initKeyBindings() { - addEventFilter(KeyEvent.KEY_PRESSED, event -> { - Optional keyBinding = preferences.getKeyBindingRepository().mapToKeyBinding(event); - if (keyBinding.isPresent()) { - switch (keyBinding.get()) { - case FOCUS_ENTRY_TABLE: - getCurrentLibraryTab().getMainTable().requestFocus(); - event.consume(); - break; - case FOCUS_GROUP_LIST: - sidePane.getSidePaneComponent(SidePaneType.GROUPS).requestFocus(); - event.consume(); - break; - case NEXT_LIBRARY: - tabbedPane.getSelectionModel().selectNext(); - event.consume(); - break; - case PREVIOUS_LIBRARY: - tabbedPane.getSelectionModel().selectPrevious(); - event.consume(); - break; - case SEARCH: - globalSearchBar.requestFocus(); - break; - case OPEN_GLOBAL_SEARCH_DIALOG: - globalSearchBar.openGlobalSearchDialog(); - break; - case NEW_ARTICLE: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.Article, dialogService, preferences, stateManager).execute(); - break; - case NEW_BOOK: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.Book, dialogService, preferences, stateManager).execute(); - break; - case NEW_INBOOK: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.InBook, dialogService, preferences, stateManager).execute(); - break; - case NEW_MASTERSTHESIS: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.MastersThesis, dialogService, preferences, stateManager).execute(); - break; - case NEW_PHDTHESIS: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.PhdThesis, dialogService, preferences, stateManager).execute(); - break; - case NEW_PROCEEDINGS: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.Proceedings, dialogService, preferences, stateManager).execute(); - break; - case NEW_TECHREPORT: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.TechReport, dialogService, preferences, stateManager).execute(); - break; - case NEW_UNPUBLISHED: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.Unpublished, dialogService, preferences, stateManager).execute(); - break; - case NEW_INPROCEEDINGS: - new NewEntryAction(this::getCurrentLibraryTab, StandardEntryType.InProceedings, dialogService, preferences, stateManager).execute(); - break; - case PASTE: - if (OS.OS_X) { // Workaround for a jdk issue that executes paste twice when using cmd+v in a TextField - // Extra workaround for CodeArea, which does not inherit from TextInputControl - if (!(stateManager.getFocusOwner().isPresent() && (stateManager.getFocusOwner().get() instanceof CodeArea))) { + addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + Optional keyBinding = + preferences.getKeyBindingRepository().mapToKeyBinding(event); + if (keyBinding.isPresent()) { + switch (keyBinding.get()) { + case FOCUS_ENTRY_TABLE: + getCurrentLibraryTab().getMainTable().requestFocus(); event.consume(); break; - } - break; + case FOCUS_GROUP_LIST: + sidePane.getSidePaneComponent(SidePaneType.GROUPS).requestFocus(); + event.consume(); + break; + case NEXT_LIBRARY: + tabbedPane.getSelectionModel().selectNext(); + event.consume(); + break; + case PREVIOUS_LIBRARY: + tabbedPane.getSelectionModel().selectPrevious(); + event.consume(); + break; + case SEARCH: + globalSearchBar.requestFocus(); + break; + case OPEN_GLOBAL_SEARCH_DIALOG: + globalSearchBar.openGlobalSearchDialog(); + break; + case NEW_ARTICLE: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.Article, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_BOOK: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.Book, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_INBOOK: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.InBook, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_MASTERSTHESIS: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.MastersThesis, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_PHDTHESIS: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.PhdThesis, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_PROCEEDINGS: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.Proceedings, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_TECHREPORT: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.TechReport, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_UNPUBLISHED: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.Unpublished, + dialogService, + preferences, + stateManager) + .execute(); + break; + case NEW_INPROCEEDINGS: + new NewEntryAction( + this::getCurrentLibraryTab, + StandardEntryType.InProceedings, + dialogService, + preferences, + stateManager) + .execute(); + break; + case PASTE: + if (OS.OS_X) { // Workaround for a jdk issue that executes paste + // twice when using cmd+v in a TextField + // Extra workaround for CodeArea, which does not inherit from + // TextInputControl + if (!(stateManager.getFocusOwner().isPresent() + && (stateManager.getFocusOwner().get() + instanceof CodeArea))) { + event.consume(); + break; + } + break; + } + break; + default: } - break; - default: - } - } - }); + } + }); } private void initBindings() { @@ -331,69 +404,98 @@ private void initBindings() { FilteredList filteredTabs = new FilteredList<>(tabbedPane.getTabs()); filteredTabs.setPredicate(LibraryTab.class::isInstance); - // This variable cannot be inlined, since otherwise the list created by EasyBind is being garbage collected - openDatabaseList = EasyBind.map(filteredTabs, tab -> ((LibraryTab) tab).getBibDatabaseContext()); + // This variable cannot be inlined, since otherwise the list created by EasyBind is being + // garbage collected + openDatabaseList = + EasyBind.map(filteredTabs, tab -> ((LibraryTab) tab).getBibDatabaseContext()); EasyBind.bindContent(stateManager.getOpenDatabases(), openDatabaseList); - // 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 -> { - if (getCurrentLibraryTab() != null) { - getCurrentLibraryTab().searchQueryProperty().set(query); - } - }); + EasyBind.subscribe( + stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH), + query -> { + if (getCurrentLibraryTab() != null) { + getCurrentLibraryTab().searchQueryProperty().set(query); + } + }); // Wait for the scene to be created, otherwise focusOwnerProperty is not provided - Platform.runLater(() -> stateManager.focusOwnerProperty().bind( - EasyBind.map(mainStage.getScene().focusOwnerProperty(), Optional::ofNullable))); - - EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), selectedTab -> { - if (selectedTab instanceof LibraryTab libraryTab) { - stateManager.setActiveDatabase(libraryTab.getBibDatabaseContext()); - stateManager.activeTabProperty().set(Optional.of(libraryTab)); - } else if (selectedTab == null) { - // All databases are closed - stateManager.setActiveDatabase(null); - stateManager.activeTabProperty().set(Optional.empty()); - } - }); + Platform.runLater( + () -> + stateManager + .focusOwnerProperty() + .bind( + EasyBind.map( + mainStage.getScene().focusOwnerProperty(), + Optional::ofNullable))); + + EasyBind.subscribe( + tabbedPane.getSelectionModel().selectedItemProperty(), + selectedTab -> { + if (selectedTab instanceof LibraryTab libraryTab) { + stateManager.setActiveDatabase(libraryTab.getBibDatabaseContext()); + stateManager.activeTabProperty().set(Optional.of(libraryTab)); + } else if (selectedTab == null) { + // All databases are closed + stateManager.setActiveDatabase(null); + stateManager.activeTabProperty().set(Optional.empty()); + } + }); /* * The following state listener makes sure focus is registered with the * correct database when the user switches tabs. Without this, * cut/paste/copy operations would sometimes occur in the wrong tab. */ - EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), tab -> { - if (!(tab instanceof LibraryTab libraryTab)) { - stateManager.setSelectedEntries(Collections.emptyList()); - mainStage.titleProperty().unbind(); - mainStage.setTitle(FRAME_TITLE); - return; - } - - // Poor-mans binding to global state - stateManager.setSelectedEntries(libraryTab.getSelectedEntries()); - - // 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()); - } - stateManager.searchResultSize(SearchType.NORMAL_SEARCH).bind(libraryTab.resultSizeProperty()); - - // Update search autocompleter with information for the correct database: - globalSearchBar.setAutoCompleter(libraryTab.getAutoCompleter()); - - libraryTab.getMainTable().requestFocus(); + EasyBind.subscribe( + tabbedPane.getSelectionModel().selectedItemProperty(), + tab -> { + if (!(tab instanceof LibraryTab libraryTab)) { + stateManager.setSelectedEntries(Collections.emptyList()); + mainStage.titleProperty().unbind(); + mainStage.setTitle(FRAME_TITLE); + return; + } - // Set window title - copy tab title - StringBinding windowTitle = Bindings.createStringBinding( - () -> libraryTab.textProperty().getValue() + " – " + FRAME_TITLE, // not a minus, but codepoint 2013 - libraryTab.textProperty()); - mainStage.titleProperty().bind(windowTitle); - }); + // Poor-mans binding to global state + stateManager.setSelectedEntries(libraryTab.getSelectedEntries()); + + // 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()); + } + stateManager + .searchResultSize(SearchType.NORMAL_SEARCH) + .bind(libraryTab.resultSizeProperty()); + + // Update search autocompleter with information for the correct database: + globalSearchBar.setAutoCompleter(libraryTab.getAutoCompleter()); + + libraryTab.getMainTable().requestFocus(); + + // Set window title - copy tab title + StringBinding windowTitle = + Bindings.createStringBinding( + () -> + libraryTab.textProperty().getValue() + + " – " + + FRAME_TITLE, // not a minus, but codepoint + // 2013 + libraryTab.textProperty()); + mainStage.titleProperty().bind(windowTitle); + }); } /* ************************************************************************ @@ -407,9 +509,9 @@ private void initBindings() { */ public @NonNull List getLibraryTabs() { return tabbedPane.getTabs().stream() - .filter(LibraryTab.class::isInstance) - .map(LibraryTab.class::cast) - .toList(); + .filter(LibraryTab.class::isInstance) + .map(LibraryTab.class::cast) + .toList(); } /** @@ -433,17 +535,18 @@ public void showLibraryTab(@NonNull LibraryTab libraryTab) { */ public void addTab(@NonNull BibDatabaseContext databaseContext, boolean raisePanel) { Objects.requireNonNull(databaseContext); - LibraryTab libraryTab = LibraryTab.createLibraryTab( - databaseContext, - this, - dialogService, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, - taskExecutor); + LibraryTab libraryTab = + LibraryTab.createLibraryTab( + databaseContext, + this, + dialogService, + preferences, + stateManager, + fileUpdateMonitor, + entryTypesManager, + undoManager, + clipBoardManager, + taskExecutor); addTab(libraryTab, raisePanel); } @@ -461,14 +564,32 @@ 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(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(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; } @@ -483,10 +604,8 @@ 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 @@ -553,7 +672,7 @@ public void handleUiCommands(List uiCommands) { /** * The action concerned with closing the window. */ - static protected class CloseAction extends SimpleCommand { + protected static class CloseAction extends SimpleCommand { private final JabRefFrame frame; @@ -569,12 +688,15 @@ public void execute() { } } - static protected class CloseDatabaseAction extends SimpleCommand { + protected static class CloseDatabaseAction extends SimpleCommand { private final LibraryTabContainer tabContainer; private final LibraryTab libraryTab; - public CloseDatabaseAction(LibraryTabContainer tabContainer, LibraryTab libraryTab, StateManager stateManager) { + public CloseDatabaseAction( + LibraryTabContainer tabContainer, + LibraryTab libraryTab, + StateManager stateManager) { this.tabContainer = tabContainer; this.libraryTab = libraryTab; this.executable.bind(ActionHelper.needsDatabase(stateManager)); @@ -589,17 +711,18 @@ public CloseDatabaseAction(LibraryTabContainer tabContainer, StateManager stateM @Override public void execute() { - Platform.runLater(() -> { - if (libraryTab == null) { - if (tabContainer.getCurrentLibraryTab() == null) { - LOGGER.error("No library tab to close"); - return; - } - tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); - } else { - tabContainer.closeTab(libraryTab); - } - }); + Platform.runLater( + () -> { + if (libraryTab == null) { + if (tabContainer.getCurrentLibraryTab() == null) { + LOGGER.error("No library tab to close"); + return; + } + tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); + } else { + tabContainer.closeTab(libraryTab); + } + }); } } @@ -644,13 +767,19 @@ public OpenDatabaseFolder(Supplier databaseContext) { @Override public void execute() { - Optional.of(databaseContext.get()).flatMap(BibDatabaseContext::getDatabasePath).ifPresent(path -> { - try { - NativeDesktop.openFolderAndSelectFile(path, preferences.getExternalApplicationsPreferences(), dialogService); - } catch (IOException e) { - LOGGER.info("Could not open folder", e); - } - }); + Optional.of(databaseContext.get()) + .flatMap(BibDatabaseContext::getDatabasePath) + .ifPresent( + path -> { + try { + NativeDesktop.openFolderAndSelectFile( + path, + preferences.getExternalApplicationsPreferences(), + dialogService); + } catch (IOException e) { + LOGGER.info("Could not open folder", e); + } + }); } } } diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java b/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java index dceb39f05ccc..d4e1e9563d0e 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java @@ -1,17 +1,5 @@ package org.jabref.gui.frame; -import java.nio.file.Path; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; - import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableBooleanValue; import javafx.scene.control.ButtonType; @@ -41,10 +29,21 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + public class JabRefFrameViewModel implements UiMessageHandler { private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrameViewModel.class); @@ -59,16 +58,17 @@ public class JabRefFrameViewModel implements UiMessageHandler { private final ClipBoardManager clipBoardManager; private final TaskExecutor taskExecutor; - public JabRefFrameViewModel(GuiPreferences preferences, - AiService aiService, - StateManager stateManager, - DialogService dialogService, - LibraryTabContainer tabContainer, - BibEntryTypesManager entryTypesManager, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + public JabRefFrameViewModel( + GuiPreferences preferences, + AiService aiService, + StateManager stateManager, + DialogService dialogService, + LibraryTabContainer tabContainer, + BibEntryTypesManager entryTypesManager, + FileUpdateMonitor fileUpdateMonitor, + UndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { this.preferences = preferences; this.aiService = aiService; this.stateManager = stateManager; @@ -83,7 +83,8 @@ 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(); @@ -103,34 +104,42 @@ public boolean close() { // 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); + Optional shouldClose = + dialogService.showBackgroundProgressDialogAndWait( + Localization.lang("Please wait..."), + 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(); - Path focusedLibraries = Optional.ofNullable(tabContainer.getCurrentLibraryTab()) - .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. + 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); + + // 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 processingLibraryDialog = + new ProcessingLibraryDialog(dialogService); processingLibraryDialog.showAndWait(tabContainer.getLibraryTabs()); return true; @@ -162,47 +171,53 @@ public void handleUiCommands(List uiCommands) { // Handle automatically setting file links uiCommands.stream() - .filter(UiCommand.AutoSetFileLinks.class::isInstance).findAny() - .map(UiCommand.AutoSetFileLinks.class::cast) - .ifPresent(autoSetFileLinks -> autoSetFileLinks(autoSetFileLinks.parserResults())); + .filter(UiCommand.AutoSetFileLinks.class::isInstance) + .findAny() + .map(UiCommand.AutoSetFileLinks.class::cast) + .ifPresent(autoSetFileLinks -> autoSetFileLinks(autoSetFileLinks.parserResults())); // 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)); + }); } private void openDatabases(List parserResults) { final List toOpenTab = new ArrayList<>(); // 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); // passed file (we take the first one) should be focused - Path focusedFile = parserResults.stream() - .findFirst() - .flatMap(ParserResult::getPath) - .orElse(preferences.getLastFilesOpenedPreferences() - .getLastFocusedFile()) - .toAbsolutePath(); + Path focusedFile = + parserResults.stream() + .findFirst() + .flatMap(ParserResult::getPath) + .orElse(preferences.getLastFilesOpenedPreferences().getLastFocusedFile()) + .toAbsolutePath(); // Add all bibDatabases databases to the frame: boolean first = false; for (ParserResult parserResult : parserResults) { // Define focused tab - if (parserResult.getPath().filter(path -> path.toAbsolutePath().equals(focusedFile)).isPresent()) { + if (parserResult + .getPath() + .filter(path -> path.toAbsolutePath().equals(focusedFile)) + .isPresent()) { first = true; } @@ -220,11 +235,10 @@ private void openDatabases(List parserResults) { undoManager, clipBoardManager, taskExecutor); - } catch ( - SQLException | - DatabaseNotSupportedException | - InvalidDBMSConnectionPropertiesException | - NotASharedDatabaseException e) { + } catch (SQLException + | DatabaseNotSupportedException + | InvalidDBMSConnectionPropertiesException + | NotASharedDatabaseException e) { LOGGER.error("Connection error", e); dialogService.showErrorDialogAndWait( Localization.lang("Connection error"), @@ -249,16 +263,23 @@ private void openDatabases(List parserResults) { } for (ParserResult parserResult : failed) { - String message = Localization.lang("Error opening file '%0'", - parserResult.getPath().map(Path::toString).orElse("(File name unknown)")) + "\n" + - parserResult.getErrorMessage(); + String message = + Localization.lang( + "Error opening file '%0'", + parserResult + .getPath() + .map(Path::toString) + .orElse("(File name unknown)")) + + "\n" + + parserResult.getErrorMessage(); dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), message); } // Display warnings, if any for (ParserResult parserResult : parserResults) { if (parserResult.hasWarnings()) { - ParserResultWarningDialog.showParserResultWarningDialog(parserResult, dialogService); + ParserResultWarningDialog.showParserResultWarningDialog( + parserResult, dialogService); getLibraryTab(parserResult).ifPresent(tabContainer::showLibraryTab); } } @@ -268,20 +289,21 @@ private void openDatabases(List parserResults) { // if we found new entry types that can be imported, or checking // if the database contents should be modified due to new features // in this version of JabRef. - parserResults.forEach(pr -> { - OpenDatabaseAction.performPostOpenActions(pr, dialogService, preferences); - if (pr.getChangedOnMigration()) { - getLibraryTab(pr).ifPresent(LibraryTab::markBaseChanged); - } - }); + parserResults.forEach( + pr -> { + OpenDatabaseAction.performPostOpenActions(pr, dialogService, preferences); + if (pr.getChangedOnMigration()) { + getLibraryTab(pr).ifPresent(LibraryTab::markBaseChanged); + } + }); LOGGER.debug("Finished adding panels"); } private Optional getLibraryTab(ParserResult parserResult) { return tabContainer.getLibraryTabs().stream() - .filter(tab -> parserResult.getDatabase().equals(tab.getDatabase())) - .findAny(); + .filter(tab -> parserResult.getDatabase().equals(tab.getDatabase())) + .findAny(); } /** @@ -301,11 +323,14 @@ private void addParserResult(ParserResult parserResult, boolean raisePanel) { } } else { // only add tab if library is not already open - Optional libraryTab = tabContainer.getLibraryTabs().stream() - .filter(p -> p.getBibDatabaseContext() - .getDatabasePath() - .equals(parserResult.getPath())) - .findFirst(); + Optional libraryTab = + tabContainer.getLibraryTabs().stream() + .filter( + p -> + p.getBibDatabaseContext() + .getDatabasePath() + .equals(parserResult.getPath())) + .findFirst(); if (libraryTab.isPresent()) { tabContainer.showLibraryTab(libraryTab.get()); @@ -322,60 +347,66 @@ 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, oldValue, newValue) -> { - if (observable != null) { - loadings.remove(observable); - } - 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()); - } - for (ObservableBooleanValue obs : loadings) { - if (obs.get()) { - // Exit the listener if any of the observables is still true - return; - } - } - // All observables are false, complete the future - LOGGER.trace("Future completed"); - future.complete(null); - }; + ChangeListener listener = + (observable, oldValue, newValue) -> { + if (observable != null) { + loadings.remove(observable); + } + 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()); + } + for (ObservableBooleanValue obs : loadings) { + if (obs.get()) { + // Exit the listener if any of the observables is still true + return; + } + } + // All observables are false, complete the future + LOGGER.trace("Future completed"); + future.complete(null); + }; for (ObservableBooleanValue obs : loadings) { obs.addListener(listener); } 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..."); - future.thenRun(() -> { - LOGGER.debug("All tabs loaded. Jumping to entry."); - for (ObservableBooleanValue obs : loadings) { - obs.removeListener(listener); - } - runnable.run(); - }); + future.thenRun( + () -> { + LOGGER.debug("All tabs loaded. Jumping to entry."); + for (ObservableBooleanValue obs : loadings) { + obs.removeListener(listener); + } + runnable.run(); + }); } 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(); + Optional bibEntry = + libraryTab.getDatabase().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()); @@ -387,7 +418,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)); } } @@ -399,7 +432,9 @@ private void jumpToEntry(String entryKey) { */ void addImportedEntries(final LibraryTab tab, final ParserResult parserResult) { BackgroundTask task = BackgroundTask.wrap(() -> parserResult); - ImportCleanup cleanup = ImportCleanup.targeting(tab.getBibDatabaseContext().getMode(), preferences.getFieldPreferences()); + ImportCleanup cleanup = + ImportCleanup.targeting( + tab.getBibDatabaseContext().getMode(), preferences.getFieldPreferences()); cleanup.doPostCleanup(parserResult.getDatabase().getEntries()); ImportEntriesDialog dialog = new ImportEntriesDialog(tab.getBibDatabaseContext(), task); dialog.setTitle(Localization.lang("Import")); @@ -408,7 +443,13 @@ void addImportedEntries(final LibraryTab tab, final ParserResult parserResult) { void autoSetFileLinks(List loaded) { for (ParserResult parserResult : loaded) { - new AutoLinkFilesAction(dialogService, preferences, stateManager, undoManager, (UiTaskExecutor) taskExecutor).execute(); + new AutoLinkFilesAction( + dialogService, + preferences, + stateManager, + undoManager, + (UiTaskExecutor) taskExecutor) + .execute(); } } } diff --git a/src/main/java/org/jabref/gui/frame/MainMenu.java b/src/main/java/org/jabref/gui/frame/MainMenu.java index 96ab72d70d4c..f9cee5a14c63 100644 --- a/src/main/java/org/jabref/gui/frame/MainMenu.java +++ b/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -1,7 +1,5 @@ package org.jabref.gui.frame; -import java.util.function.Supplier; - import javafx.event.ActionEvent; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; @@ -85,6 +83,8 @@ import org.jabref.model.entry.field.SpecialField; import org.jabref.model.util.FileUpdateMonitor; +import java.util.function.Supplier; + public class MainMenu extends MenuBar { private final JabRefFrame frame; private final FileHistoryMenu fileHistoryMenu; @@ -102,21 +102,22 @@ public class MainMenu extends MenuBar { private final Supplier openDatabaseActionSupplier; private final AiService aiService; - public MainMenu(JabRefFrame frame, - FileHistoryMenu fileHistoryMenu, - SidePane sidePane, - PushToApplicationCommand pushToApplicationCommand, - GuiPreferences preferences, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - DialogService dialogService, - JournalAbbreviationRepository abbreviationRepository, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - Supplier openDatabaseActionSupplier, - AiService aiService) { + public MainMenu( + JabRefFrame frame, + FileHistoryMenu fileHistoryMenu, + SidePane sidePane, + PushToApplicationCommand pushToApplicationCommand, + GuiPreferences preferences, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + TaskExecutor taskExecutor, + DialogService dialogService, + JournalAbbreviationRepository abbreviationRepository, + BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, + ClipBoardManager clipBoardManager, + Supplier openDatabaseActionSupplier, + AiService aiService) { this.frame = frame; this.fileHistoryMenu = fileHistoryMenu; this.sidePane = sidePane; @@ -147,245 +148,694 @@ 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)), - 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(), - - 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))), - - 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)), + 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(), + 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))), + 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)); - 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. - if (OS.OS_X && !frame.getMainStage().focusedProperty().get()) { - event.consume(); - } - }); - - MenuItem newEntryFromPlainTextOnline = factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE, new ExtractBibtexActionOnline(dialogService, preferences, stateManager, true)); - library.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - newEntryFromPlainTextOnline, - factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT_OFFLINE, new ExtractBibtexActionOffline(dialogService, stateManager)), - factory.createMenuItem(StandardActions.DELETE_ENTRY, new EditAction(StandardActions.DELETE_ENTRY, frame::getCurrentLibraryTab, stateManager, undoManager)), - - 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.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.UNABBREVIATE, new AbbreviateAction(StandardActions.UNABBREVIATE, frame::getCurrentLibraryTab, dialogService, stateManager, preferences.getJournalAbbreviationPreferences(), abbreviationRepository, taskExecutor, undoManager)) - ); + 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. + if (OS.OS_X && !frame.getMainStage().focusedProperty().get()) { + event.consume(); + } + }); + + MenuItem newEntryFromPlainTextOnline = + factory.createMenuItem( + StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE, + new ExtractBibtexActionOnline( + dialogService, preferences, stateManager, true)); + library.getItems() + .addAll( + factory.createMenuItem( + StandardActions.NEW_ENTRY, + new NewEntryAction( + frame::getCurrentLibraryTab, + dialogService, + preferences, + stateManager)), + newEntryFromPlainTextOnline, + factory.createMenuItem( + StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT_OFFLINE, + new ExtractBibtexActionOffline(dialogService, stateManager)), + factory.createMenuItem( + StandardActions.DELETE_ENTRY, + new EditAction( + StandardActions.DELETE_ENTRY, + frame::getCurrentLibraryTab, + stateManager, + undoManager)), + 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.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.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); - lookupIdentifiers.getItems().add(factory.createMenuItem(identifierAction.getAction(), identifierAction)); + for (IdFetcher fetcher : + WebFetchers.getIdFetchers(preferences.getImportFormatPreferences())) { + LookupIdentifierAction identifierAction = + new LookupIdentifierAction<>( + fetcher, stateManager, undoManager, dialogService, taskExecutor); + lookupIdentifiers + .getItems() + .add(factory.createMenuItem(identifierAction.getAction(), identifierAction)); } - lookup.getItems().addAll( - lookupIdentifiers, - factory.createMenuItem(StandardActions.DOWNLOAD_FULL_TEXT, new DownloadFullTextAction(dialogService, stateManager, preferences, (UiTaskExecutor) taskExecutor)), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(dialogService, stateManager)) - ); + lookup.getItems() + .addAll( + lookupIdentifiers, + factory.createMenuItem( + StandardActions.DOWNLOAD_FULL_TEXT, + new DownloadFullTextAction( + dialogService, + stateManager, + preferences, + (UiTaskExecutor) taskExecutor)), + new SeparatorMenuItem(), + factory.createMenuItem( + StandardActions.FIND_UNLINKED_FILES, + new FindUnlinkedFilesAction(dialogService, stateManager))); - final MenuItem pushToApplicationMenuItem = factory.createMenuItem(pushToApplicationCommand.getAction(), pushToApplicationCommand); + 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(), + 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(), - factory.createMenuItem(StandardActions.REDOWNLOAD_MISSING_FILES, new RedownloadMissingFilesAction(stateManager, dialogService, preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), taskExecutor)) - ); + // 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, frame::getCurrentLibraryTab, stateManager)), - factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, new PreviewSwitchAction(PreviewSwitchAction.Direction.PREVIOUS, frame::getCurrentLibraryTab, 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("http://discourse.jabref.org/", dialogService, preferences.getExternalApplicationsPreferences())), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.ERROR_CONSOLE, new ErrorConsoleAction()), - - new SeparatorMenuItem(), - - factory.createMenuItem(StandardActions.DONATE, new OpenBrowserAction("https://donations.jabref.org", 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("https://jabref.org/", dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction("https://blog.jabref.org/", dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/", dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org", dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_GITHUB, new OpenBrowserAction("https://github.com/JabRef/jabref", dialogService, preferences.getExternalApplicationsPreferences())), - + 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.OPEN_DEV_VERSION_LINK, new OpenBrowserAction("https://builds.jabref.org/master/", dialogService, preferences.getExternalApplicationsPreferences())), - factory.createMenuItem(StandardActions.OPEN_CHANGELOG, new OpenBrowserAction("https://github.com/JabRef/jabref/blob/main/CHANGELOG.md", dialogService, preferences.getExternalApplicationsPreferences())) - ), - factory.createMenuItem(StandardActions.ABOUT, new AboutAction()) - ); + factory.createMenuItem( + StandardActions.NEXT_PREVIEW_STYLE, + new PreviewSwitchAction( + PreviewSwitchAction.Direction.NEXT, + frame::getCurrentLibraryTab, + stateManager)), + factory.createMenuItem( + StandardActions.PREVIOUS_PREVIEW_STYLE, + new PreviewSwitchAction( + PreviewSwitchAction.Direction.PREVIOUS, + frame::getCurrentLibraryTab, + 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( + "http://discourse.jabref.org/", + dialogService, + preferences.getExternalApplicationsPreferences())), + new SeparatorMenuItem(), + factory.createMenuItem( + StandardActions.ERROR_CONSOLE, new ErrorConsoleAction()), + new SeparatorMenuItem(), + factory.createMenuItem( + StandardActions.DONATE, + new OpenBrowserAction( + "https://donations.jabref.org", + 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( + "https://jabref.org/", + dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem( + StandardActions.OPEN_BLOG, + new OpenBrowserAction( + "https://blog.jabref.org/", + dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem( + StandardActions.OPEN_FACEBOOK, + new OpenBrowserAction( + "https://www.facebook.com/JabRef/", + dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem( + StandardActions.OPEN_TWITTER, + new OpenBrowserAction( + "https://twitter.com/jabref_org", + dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem( + StandardActions.OPEN_GITHUB, + new OpenBrowserAction( + "https://github.com/JabRef/jabref", + dialogService, + preferences.getExternalApplicationsPreferences())), + new SeparatorMenuItem(), + factory.createMenuItem( + StandardActions.OPEN_DEV_VERSION_LINK, + new OpenBrowserAction( + "https://builds.jabref.org/master/", + dialogService, + preferences.getExternalApplicationsPreferences())), + factory.createMenuItem( + StandardActions.OPEN_CHANGELOG, + new OpenBrowserAction( + "https://github.com/JabRef/jabref/blob/main/CHANGELOG.md", + dialogService, + preferences.getExternalApplicationsPreferences()))), + factory.createMenuItem(StandardActions.ABOUT, new AboutAction())); // @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/src/main/java/org/jabref/gui/frame/MainToolBar.java b/src/main/java/org/jabref/gui/frame/MainToolBar.java index e07467e2c3d1..534698dfcfe4 100644 --- a/src/main/java/org/jabref/gui/frame/MainToolBar.java +++ b/src/main/java/org/jabref/gui/frame/MainToolBar.java @@ -1,5 +1,8 @@ package org.jabref.gui.frame; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; + import javafx.concurrent.Task; import javafx.geometry.Orientation; import javafx.scene.Group; @@ -13,6 +16,8 @@ import javafx.scene.layout.Region; import javafx.scene.shape.Rectangle; +import org.controlsfx.control.PopOver; +import org.controlsfx.control.TaskProgressView; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; @@ -45,11 +50,6 @@ import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.util.FileUpdateMonitor; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.Subscription; -import org.controlsfx.control.PopOver; -import org.controlsfx.control.TaskProgressView; - public class MainToolBar extends ToolBar { private final LibraryTabContainer frame; private final PushToApplicationCommand pushToApplicationCommand; @@ -68,18 +68,19 @@ public class MainToolBar extends ToolBar { private PopOver progressViewPopOver; private Subscription taskProgressSubscription; - public MainToolBar(LibraryTabContainer tabContainer, - PushToApplicationCommand 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, + PushToApplicationCommand 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; @@ -102,59 +103,146 @@ 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 // The action itself asks the user if it is OK to use Grobid (in some cases). - // Therefore, the condition of enablement is "only" if a library is opened. (Parameter "false") - Button newEntryFromPlainTextOnlineButton = factory.createIconButton(StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT, new ExtractBibtexActionOnline(dialogService, preferences, stateManager, false)); - - 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.NEW_ARTICLE, new NewEntryAction(frame::getCurrentLibraryTab, StandardEntryType.Article, dialogService, preferences, stateManager)), - factory.createIconButton(StandardActions.NEW_ENTRY, new NewEntryAction(frame::getCurrentLibraryTab, dialogService, preferences, stateManager)), - createNewEntryFromIdButton(), - newEntryFromPlainTextOnlineButton, - 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())))); + // Therefore, the condition of enablement is "only" if a library is opened. (Parameter + // "false") + Button newEntryFromPlainTextOnlineButton = + factory.createIconButton( + StandardActions.NEW_ENTRY_FROM_PLAIN_TEXT, + new ExtractBibtexActionOnline( + dialogService, preferences, stateManager, false)); + + 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.NEW_ARTICLE, + new NewEntryAction( + frame::getCurrentLibraryTab, + StandardEntryType.Article, + dialogService, + preferences, + stateManager)), + factory.createIconButton( + StandardActions.NEW_ENTRY, + new NewEntryAction( + frame::getCurrentLibraryTab, + dialogService, + preferences, + stateManager)), + createNewEntryFromIdButton(), + newEntryFromPlainTextOnlineButton, + 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); @@ -172,24 +260,31 @@ Button createNewEntryFromIdButton() { newEntryFromIdButton.getStyleClass().setAll("icon-button"); newEntryFromIdButton.setFocusTraversable(false); newEntryFromIdButton.disableProperty().bind(ActionHelper.needsDatabase(stateManager).not()); - newEntryFromIdButton.setOnMouseClicked(event -> { - GenerateEntryFromIdDialog entryFromId = new GenerateEntryFromIdDialog(frame.getCurrentLibraryTab(), dialogService, preferences, taskExecutor, stateManager); - - if (entryFromIdPopOver == null) { - entryFromIdPopOver = new PopOver(entryFromId.getDialogPane()); - entryFromIdPopOver.setTitle(Localization.lang("Import by ID")); - entryFromIdPopOver.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER); - entryFromIdPopOver.setContentNode(entryFromId.getDialogPane()); - entryFromIdPopOver.show(newEntryFromIdButton); - entryFromId.setEntryFromIdPopOver(entryFromIdPopOver); - } else if (entryFromIdPopOver.isShowing()) { - entryFromIdPopOver.hide(); - } else { - entryFromIdPopOver.setContentNode(entryFromId.getDialogPane()); - entryFromIdPopOver.show(newEntryFromIdButton); - entryFromId.setEntryFromIdPopOver(entryFromIdPopOver); - } - }); + newEntryFromIdButton.setOnMouseClicked( + event -> { + GenerateEntryFromIdDialog entryFromId = + new GenerateEntryFromIdDialog( + frame.getCurrentLibraryTab(), + dialogService, + preferences, + taskExecutor, + stateManager); + + if (entryFromIdPopOver == null) { + entryFromIdPopOver = new PopOver(entryFromId.getDialogPane()); + entryFromIdPopOver.setTitle(Localization.lang("Import by ID")); + entryFromIdPopOver.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER); + entryFromIdPopOver.setContentNode(entryFromId.getDialogPane()); + entryFromIdPopOver.show(newEntryFromIdButton); + entryFromId.setEntryFromIdPopOver(entryFromIdPopOver); + } else if (entryFromIdPopOver.isShowing()) { + entryFromIdPopOver.hide(); + } else { + entryFromIdPopOver.setContentNode(entryFromId.getDialogPane()); + entryFromIdPopOver.show(newEntryFromIdButton); + entryFromId.setEntryFromIdPopOver(entryFromIdPopOver); + } + }); newEntryFromIdButton.setTooltip(new Tooltip(Localization.lang("Import by ID"))); return newEntryFromIdButton; @@ -203,49 +298,65 @@ Group createTaskIndicator() { Tooltip someTasksRunning = new Tooltip(Localization.lang("Background Tasks are running")); Tooltip noTasksRunning = new Tooltip(Localization.lang("Background Tasks are done")); indicator.setTooltip(noTasksRunning); - stateManager.getAnyTaskRunning().addListener((observable, oldValue, newValue) -> { - if (newValue) { - indicator.setTooltip(someTasksRunning); - } else { - indicator.setTooltip(noTasksRunning); - } - }); + stateManager + .getAnyTaskRunning() + .addListener( + (observable, oldValue, newValue) -> { + if (newValue) { + indicator.setTooltip(someTasksRunning); + } else { + indicator.setTooltip(noTasksRunning); + } + }); // The label of the indicator cannot be removed with styling. Therefore, // 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. - if (!indicator.isIndeterminate()) { - indicator.setPrefWidth(newValue.doubleValue()); - } - if (newValue.doubleValue() > 0) { - Rectangle clip = new Rectangle(newValue.doubleValue(), newValue.doubleValue()); - indicator.setClip(clip); - } - }); - - indicator.setOnMouseClicked(event -> { - if ((progressViewPopOver != null) && (progressViewPopOver.isShowing())) { - progressViewPopOver.hide(); - taskProgressSubscription.unsubscribe(); - return; - } - - TaskProgressView> taskProgressView = new TaskProgressView<>(); - taskProgressSubscription = EasyBind.bindContent(taskProgressView.getTasks(), stateManager.getRunningBackgroundTasks()); - taskProgressView.setRetainTasks(false); - taskProgressView.setGraphicFactory(task -> ThemeManager.getDownloadIconTitleMap.getOrDefault(task.getTitle(), null)); - - if (progressViewPopOver == null) { - progressViewPopOver = new PopOver(taskProgressView); - progressViewPopOver.setTitle(Localization.lang("Background Tasks")); - progressViewPopOver.setArrowLocation(PopOver.ArrowLocation.RIGHT_TOP); - } - - progressViewPopOver.setContentNode(taskProgressView); - progressViewPopOver.show(indicator); - }); + 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. + if (!indicator.isIndeterminate()) { + indicator.setPrefWidth(newValue.doubleValue()); + } + if (newValue.doubleValue() > 0) { + Rectangle clip = + new Rectangle( + newValue.doubleValue(), newValue.doubleValue()); + indicator.setClip(clip); + } + }); + + indicator.setOnMouseClicked( + event -> { + if ((progressViewPopOver != null) && (progressViewPopOver.isShowing())) { + progressViewPopOver.hide(); + taskProgressSubscription.unsubscribe(); + return; + } + + TaskProgressView> taskProgressView = new TaskProgressView<>(); + taskProgressSubscription = + EasyBind.bindContent( + taskProgressView.getTasks(), + stateManager.getRunningBackgroundTasks()); + taskProgressView.setRetainTasks(false); + taskProgressView.setGraphicFactory( + task -> + ThemeManager.getDownloadIconTitleMap.getOrDefault( + task.getTitle(), null)); + + if (progressViewPopOver == null) { + progressViewPopOver = new PopOver(taskProgressView); + progressViewPopOver.setTitle(Localization.lang("Background Tasks")); + progressViewPopOver.setArrowLocation(PopOver.ArrowLocation.RIGHT_TOP); + } + + progressViewPopOver.setContentNode(taskProgressView); + progressViewPopOver.show(indicator); + }); return new Group(indicator); } diff --git a/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java b/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java index c29c86a5d027..dc51b823fa55 100644 --- a/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java +++ b/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java @@ -1,9 +1,5 @@ package org.jabref.gui.frame; -import java.io.IOException; -import java.util.Optional; -import java.util.function.Supplier; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -11,10 +7,13 @@ import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.model.database.BibDatabaseContext; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.Optional; +import java.util.function.Supplier; + public class OpenConsoleAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(OpenConsoleAction.class); @@ -29,7 +28,11 @@ public class OpenConsoleAction extends SimpleCommand { * {@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; @@ -41,18 +44,23 @@ public OpenConsoleAction(Supplier databaseContext, StateMana /** * Using this constructor will result in executing the command on the active database. */ - public OpenConsoleAction(StateManager stateManager, GuiPreferences preferences, DialogService dialogService) { + public OpenConsoleAction( + StateManager stateManager, GuiPreferences preferences, DialogService dialogService) { this(() -> null, stateManager, preferences, dialogService); } @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/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java b/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java index 72ccfbba08fd..b2f7ac2c550a 100644 --- a/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java +++ b/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java @@ -1,7 +1,5 @@ package org.jabref.gui.frame; -import java.util.List; - import javafx.concurrent.Task; import org.jabref.gui.DialogService; @@ -9,6 +7,8 @@ import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.l10n.Localization; +import java.util.List; + /** * Dialog shown when closing of application needs to wait for a save operation to finish. */ @@ -22,26 +22,26 @@ public ProcessingLibraryDialog(DialogService dialogService) { public void showAndWait(List libraryTabs) { if (libraryTabs.stream().anyMatch(tab -> tab.isSaving())) { - Task waitForSaveFinished = new Task<>() { - @Override - protected Void call() throws Exception { - while (libraryTabs.stream().anyMatch(tab -> tab.isSaving())) { - if (isCancelled()) { + Task waitForSaveFinished = + new Task<>() { + @Override + protected Void call() throws Exception { + while (libraryTabs.stream().anyMatch(tab -> tab.isSaving())) { + if (isCancelled()) { + return null; + } else { + Thread.sleep(100); + } + } return null; - } else { - Thread.sleep(100); } - } - return null; - } - }; + }; UiTaskExecutor.runInJavaFXThread(waitForSaveFinished); dialogService.showProgressDialogAndWait( Localization.lang("Please wait..."), Localization.lang("Waiting for save operation to finish..."), - waitForSaveFinished - ); + waitForSaveFinished); } } } diff --git a/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java b/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java index 194fc4c4a200..d27edb712c8c 100644 --- a/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java +++ b/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java @@ -1,13 +1,5 @@ package org.jabref.gui.frame; -import java.awt.Desktop; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -20,10 +12,17 @@ import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + /** * Sends the selected entry as email *

    @@ -43,10 +42,11 @@ public abstract class SendAsEMailAction extends SimpleCommand { 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; @@ -56,13 +56,14 @@ 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); + .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 Exception { @@ -102,17 +103,26 @@ 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 - boolean openFolders = preferences.getExternalApplicationsPreferences().shouldAutoOpenEmailAttachmentsFolder(); + 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); + NativeDesktop.openFolderAndSelectFile( + path.toAbsolutePath(), + preferences.getExternalApplicationsPreferences(), + dialogService); } catch (IOException e) { LOGGER.debug("Cannot open file", e); } diff --git a/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java b/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java index 23df424a3c93..6080cf20024d 100644 --- a/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java +++ b/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java @@ -14,10 +14,16 @@ 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 diff --git a/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java b/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java index 3e379f9533af..bdd059bf80b3 100644 --- a/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java +++ b/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java @@ -1,9 +1,5 @@ package org.jabref.gui.frame; -import java.io.IOException; -import java.io.StringWriter; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -16,10 +12,13 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; + /** * Sends the selected entries to any specifiable email * by populating the email body @@ -30,11 +29,12 @@ public class SendAsStandardEmailAction extends SendAsEMailAction { 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,7 +59,9 @@ 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 { diff --git a/src/main/java/org/jabref/gui/frame/SidePanePreferences.java b/src/main/java/org/jabref/gui/frame/SidePanePreferences.java index 86c6751d6593..64730a7b1c38 100644 --- a/src/main/java/org/jabref/gui/frame/SidePanePreferences.java +++ b/src/main/java/org/jabref/gui/frame/SidePanePreferences.java @@ -1,8 +1,5 @@ package org.jabref.gui.frame; -import java.util.Map; -import java.util.Set; - import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; @@ -11,14 +8,18 @@ import org.jabref.gui.sidepane.SidePaneType; +import java.util.Map; +import java.util.Set; + 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); diff --git a/src/main/java/org/jabref/gui/frame/UiMessageHandler.java b/src/main/java/org/jabref/gui/frame/UiMessageHandler.java index 38a41d7d83cb..dd3a60bf104e 100644 --- a/src/main/java/org/jabref/gui/frame/UiMessageHandler.java +++ b/src/main/java/org/jabref/gui/frame/UiMessageHandler.java @@ -1,9 +1,9 @@ package org.jabref.gui.frame; -import java.util.List; - import org.jabref.logic.UiCommand; +import java.util.List; + /** * Specifies an interface that can process either cli or remote commands to the ui * diff --git a/src/main/java/org/jabref/gui/groups/GroupColorPicker.java b/src/main/java/org/jabref/gui/groups/GroupColorPicker.java index 78fcafe65cf6..40ee2349853b 100644 --- a/src/main/java/org/jabref/gui/groups/GroupColorPicker.java +++ b/src/main/java/org/jabref/gui/groups/GroupColorPicker.java @@ -1,12 +1,12 @@ package org.jabref.gui.groups; -import java.util.List; - import javafx.scene.paint.Color; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.List; + @NullMarked public class GroupColorPicker { diff --git a/src/main/java/org/jabref/gui/groups/GroupDescriptions.java b/src/main/java/org/jabref/gui/groups/GroupDescriptions.java index 5023f77e5f01..ff5273e3d5fe 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDescriptions.java +++ b/src/main/java/org/jabref/gui/groups/GroupDescriptions.java @@ -8,10 +8,10 @@ public class GroupDescriptions { - private GroupDescriptions() { - } + private GroupDescriptions() {} - public static String getShortDescriptionKeywordGroup(KeywordGroup keywordGroup, boolean showDynamic) { + public static String getShortDescriptionKeywordGroup( + KeywordGroup keywordGroup, boolean showDynamic) { StringBuilder sb = new StringBuilder(); sb.append(""); if (showDynamic) { @@ -43,7 +43,10 @@ public static String getShortDescriptionKeywordGroup(KeywordGroup keywordGroup, public static String getShortDescriptionExplicitGroup(ExplicitGroup explicitGroup) { StringBuilder sb = new StringBuilder(); - sb.append("").append(explicitGroup.getName()).append(" - ").append(Localization.lang("static group")); + sb.append("") + .append(explicitGroup.getName()) + .append(" - ") + .append(Localization.lang("static group")); switch (explicitGroup.getHierarchicalContext()) { case INCLUDING: sb.append(", ").append(Localization.lang("includes subgroups")); @@ -73,7 +76,9 @@ public static String getShortDescription(SearchGroup searchGroup, boolean showDy sb.append(Localization.lang("dynamic group")); sb.append(" ("); sb.append(Localization.lang("search expression")); - sb.append(" ").append(StringUtil.quoteForHTML(searchGroup.getSearchExpression())).append(")"); + sb.append(" ") + .append(StringUtil.quoteForHTML(searchGroup.getSearchExpression())) + .append(")"); switch (searchGroup.getHierarchicalContext()) { case INCLUDING: sb.append(", ").append(Localization.lang("includes subgroups")); diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java b/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java index 0cf5d92b173b..e272ce634b2b 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialogHeader.java @@ -1,5 +1,6 @@ package org.jabref.gui.groups; public enum GroupDialogHeader { - GROUP, SUBGROUP + GROUP, + SUBGROUP } diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogView.java b/src/main/java/org/jabref/gui/groups/GroupDialogView.java index c1f537248ead..e7fe9891950d 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialogView.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialogView.java @@ -1,10 +1,10 @@ package org.jabref.gui.groups; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; -import java.util.ServiceLoader; +import com.airhacks.afterburner.views.ViewLoader; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.collections.FXCollections; @@ -29,6 +29,10 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; +import org.controlsfx.control.GridCell; +import org.controlsfx.control.GridView; +import org.controlsfx.control.PopOver; +import org.controlsfx.control.textfield.CustomTextField; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; @@ -47,19 +51,17 @@ import org.jabref.model.groups.GroupHierarchyType; import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.views.ViewLoader; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; -import org.controlsfx.control.GridCell; -import org.controlsfx.control.GridView; -import org.controlsfx.control.PopOver; -import org.controlsfx.control.textfield.CustomTextField; import org.jspecify.annotations.Nullable; import org.kordamp.ikonli.Ikon; import org.kordamp.ikonli.IkonProvider; import org.kordamp.ikonli.javafx.FontIcon; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; + public class GroupDialogView extends BaseDialog { private static boolean useAutoColoring = false; @@ -98,8 +100,10 @@ public class GroupDialogView extends BaseDialog { @FXML private TextField texGroupFilePath; - private final EnumMap hierarchyText = new EnumMap<>(GroupHierarchyType.class); - private final EnumMap hierarchyToolTip = new EnumMap<>(GroupHierarchyType.class); + private final EnumMap hierarchyText = + new EnumMap<>(GroupHierarchyType.class); + private final EnumMap hierarchyToolTip = + new EnumMap<>(GroupHierarchyType.class); private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); @@ -114,17 +118,16 @@ public class GroupDialogView extends BaseDialog { @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) { @@ -143,19 +146,24 @@ 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 -> { - helpAction.execute(); - event.consume(); - }); + helpButton.addEventFilter( + ActionEvent.ACTION, + event -> { + helpAction.execute(); + event.consume(); + }); - confirmDialogButton.disableProperty().bind(viewModel.validationStatus().validProperty().not()); + confirmDialogButton + .disableProperty() + .bind(viewModel.validationStatus().validProperty().not()); // handle validation before closing dialog and calling resultConverter confirmDialogButton.addEventFilter(ActionEvent.ACTION, viewModel::validationHandler); } @@ -170,16 +178,33 @@ public GroupDialogView(BibDatabaseContext currentDatabase, @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()); @@ -191,7 +216,9 @@ public void initialize() { .withText(hierarchyText::get) .withStringTooltip(hierarchyToolTip::get) .install(hierarchicalContextCombo); - hierarchicalContextCombo.valueProperty().bindBidirectional(viewModel.groupHierarchySelectedProperty()); + hierarchicalContextCombo + .valueProperty() + .bindBidirectional(viewModel.groupHierarchySelectedProperty()); explicitRadioButton.selectedProperty().bindBidirectional(viewModel.typeExplicitProperty()); keywordsRadioButton.selectedProperty().bindBidirectional(viewModel.typeKeywordsProperty()); @@ -199,53 +226,96 @@ public void initialize() { autoRadioButton.selectedProperty().bindBidirectional(viewModel.typeAutoProperty()); texRadioButton.selectedProperty().bindBidirectional(viewModel.typeTexProperty()); - keywordGroupSearchTerm.textProperty().bindBidirectional(viewModel.keywordGroupSearchTermProperty()); - keywordGroupSearchField.textProperty().bindBidirectional(viewModel.keywordGroupSearchFieldProperty()); - keywordGroupCaseSensitive.selectedProperty().bindBidirectional(viewModel.keywordGroupCaseSensitiveProperty()); - keywordGroupRegex.selectedProperty().bindBidirectional(viewModel.keywordGroupRegexProperty()); - - searchGroupSearchTerm.textProperty().bindBidirectional(viewModel.searchGroupSearchTermProperty()); - - autoGroupKeywordsOption.selectedProperty().bindBidirectional(viewModel.autoGroupKeywordsOptionProperty()); - autoGroupKeywordsField.textProperty().bindBidirectional(viewModel.autoGroupKeywordsFieldProperty()); - autoGroupKeywordsDeliminator.textProperty().bindBidirectional(viewModel.autoGroupKeywordsDeliminatorProperty()); - autoGroupKeywordsHierarchicalDeliminator.textProperty().bindBidirectional(viewModel.autoGroupKeywordsHierarchicalDeliminatorProperty()); - autoGroupPersonsOption.selectedProperty().bindBidirectional(viewModel.autoGroupPersonsOptionProperty()); - autoGroupPersonsField.textProperty().bindBidirectional(viewModel.autoGroupPersonsFieldProperty()); + keywordGroupSearchTerm + .textProperty() + .bindBidirectional(viewModel.keywordGroupSearchTermProperty()); + keywordGroupSearchField + .textProperty() + .bindBidirectional(viewModel.keywordGroupSearchFieldProperty()); + keywordGroupCaseSensitive + .selectedProperty() + .bindBidirectional(viewModel.keywordGroupCaseSensitiveProperty()); + keywordGroupRegex + .selectedProperty() + .bindBidirectional(viewModel.keywordGroupRegexProperty()); + + searchGroupSearchTerm + .textProperty() + .bindBidirectional(viewModel.searchGroupSearchTermProperty()); + + autoGroupKeywordsOption + .selectedProperty() + .bindBidirectional(viewModel.autoGroupKeywordsOptionProperty()); + autoGroupKeywordsField + .textProperty() + .bindBidirectional(viewModel.autoGroupKeywordsFieldProperty()); + autoGroupKeywordsDeliminator + .textProperty() + .bindBidirectional(viewModel.autoGroupKeywordsDeliminatorProperty()); + autoGroupKeywordsHierarchicalDeliminator + .textProperty() + .bindBidirectional(viewModel.autoGroupKeywordsHierarchicalDeliminatorProperty()); + autoGroupPersonsOption + .selectedProperty() + .bindBidirectional(viewModel.autoGroupPersonsOptionProperty()); + autoGroupPersonsField + .textProperty() + .bindBidirectional(viewModel.autoGroupPersonsFieldProperty()); texGroupFilePath.textProperty().bindBidirectional(viewModel.texGroupFilePathProperty()); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> { - validationVisualizer.initVisualization(viewModel.nameValidationStatus(), nameField); - validationVisualizer.initVisualization(viewModel.nameContainsDelimiterValidationStatus(), nameField, false); - validationVisualizer.initVisualization(viewModel.sameNameValidationStatus(), nameField); - validationVisualizer.initVisualization(viewModel.searchSearchTermEmptyValidationStatus(), searchGroupSearchTerm); - validationVisualizer.initVisualization(viewModel.keywordRegexValidationStatus(), keywordGroupSearchTerm); - validationVisualizer.initVisualization(viewModel.keywordSearchTermEmptyValidationStatus(), keywordGroupSearchTerm); - validationVisualizer.initVisualization(viewModel.keywordFieldEmptyValidationStatus(), keywordGroupSearchField); - validationVisualizer.initVisualization(viewModel.texGroupFilePathValidatonStatus(), texGroupFilePath); - nameField.requestFocus(); - }); + Platform.runLater( + () -> { + validationVisualizer.initVisualization( + viewModel.nameValidationStatus(), nameField); + validationVisualizer.initVisualization( + viewModel.nameContainsDelimiterValidationStatus(), nameField, false); + validationVisualizer.initVisualization( + viewModel.sameNameValidationStatus(), nameField); + validationVisualizer.initVisualization( + viewModel.searchSearchTermEmptyValidationStatus(), + searchGroupSearchTerm); + validationVisualizer.initVisualization( + viewModel.keywordRegexValidationStatus(), keywordGroupSearchTerm); + validationVisualizer.initVisualization( + viewModel.keywordSearchTermEmptyValidationStatus(), + keywordGroupSearchTerm); + validationVisualizer.initVisualization( + viewModel.keywordFieldEmptyValidationStatus(), keywordGroupSearchField); + validationVisualizer.initVisualization( + viewModel.texGroupFilePathValidatonStatus(), texGroupFilePath); + nameField.requestFocus(); + }); autoColorCheckbox.setSelected(useAutoColoring); - autoColorCheckbox.setOnAction(event -> { - useAutoColoring = autoColorCheckbox.isSelected(); - if (!autoColorCheckbox.isSelected()) { - return; - } - if (parentNode == null) { - viewModel.colorFieldProperty().setValue(IconTheme.getDefaultGroupColor()); - return; - } - 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)); - viewModel.colorFieldProperty().setValue(color); - }); + autoColorCheckbox.setOnAction( + event -> { + useAutoColoring = autoColorCheckbox.isSelected(); + if (!autoColorCheckbox.isSelected()) { + return; + } + if (parentNode == null) { + viewModel.colorFieldProperty().setValue(IconTheme.getDefaultGroupColor()); + return; + } + 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)); + viewModel.colorFieldProperty().setValue(color); + }); } @FXML @@ -258,7 +328,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())); } @@ -267,9 +338,16 @@ 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()); @@ -284,9 +362,10 @@ private void openIconPicker() { // Necessary because of a bug in controlsfx GridView // https://github.com/controlsfx/controlsfx/issues/1400 // The issue is closed, but still appears here - Platform.runLater(() -> { - ikonGridView.setItems(filteredList); - }); + Platform.runLater( + () -> { + ikonGridView.setItems(filteredList); + }); PopOver popOver = new PopOver(vBox); popOver.setDetachable(false); @@ -310,13 +389,23 @@ protected void updateItem(Ikon ikon, boolean empty) { setGraphic(fontIcon); setAlignment(Pos.BASELINE_CENTER); setPadding(new Insets(1)); - setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN))); - - setOnMouseClicked(event -> { - iconField.textProperty().setValue(String.valueOf(fontIcon.getIconCode())); - PopOver stage = (PopOver) this.getGridView().getParent().getScene().getWindow(); - stage.hide(); - }); + setBorder( + new Border( + new BorderStroke( + Color.BLACK, + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + BorderStroke.THIN))); + + setOnMouseClicked( + event -> { + iconField + .textProperty() + .setValue(String.valueOf(fontIcon.getIconCode())); + PopOver stage = + (PopOver) this.getGridView().getParent().getScene().getWindow(); + stage.hide(); + }); } } } diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java index 1c36289b9dbb..923d2bb9b61c 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java @@ -1,14 +1,10 @@ package org.jabref.gui.groups; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -54,14 +50,18 @@ import org.jabref.model.search.SearchFlags; import org.jabref.model.strings.StringUtil; import org.jabref.model.util.FileUpdateMonitor; - -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; import org.jspecify.annotations.Nullable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + public class GroupDialogViewModel { // Basic Settings private final StringProperty nameProperty = new SimpleStringProperty(""); @@ -69,8 +69,10 @@ public class GroupDialogViewModel { 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<>(); + private final ListProperty groupHierarchyListProperty = + new SimpleListProperty<>(); + private final ObjectProperty groupHierarchySelectedProperty = + new SimpleObjectProperty<>(); // Type private final BooleanProperty typeExplicitProperty = new SimpleBooleanProperty(); @@ -86,12 +88,14 @@ public class GroupDialogViewModel { 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 StringProperty autoGroupKeywordsHierarchicalDelimiterProperty = + new SimpleStringProperty(""); private final BooleanProperty autoGroupPersonsOptionProperty = new SimpleBooleanProperty(); private final StringProperty autoGroupPersonsFieldProperty = new SimpleStringProperty(""); @@ -115,13 +119,14 @@ public class GroupDialogViewModel { 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; @@ -137,129 +142,167 @@ public GroupDialogViewModel(DialogService dialogService, private void setupValidation() { validator = new CompositeValidator(); - nameValidator = new FunctionBasedValidator<>( - nameProperty, - StringUtil::isNotBlank, - ValidationMessage.error(Localization.lang("Please enter a name for the group."))); - - 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()) { - int groupsWithSameName = rootGroup.get().findChildrenSatisfying(group -> group.getName().equals(name)).size(); - if ((editedGroup == null) && (groupsWithSameName > 0)) { - // 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) || (groupsWithSameName <= 0); - } - 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.getValue()) { - return true; + nameValidator = + new FunctionBasedValidator<>( + nameProperty, + StringUtil::isNotBlank, + ValidationMessage.error( + Localization.lang("Please enter a name for the group."))); + + 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()) { + int groupsWithSameName = + rootGroup + .get() + .findChildrenSatisfying( + group -> group.getName().equals(name)) + .size(); + if ((editedGroup == null) && (groupsWithSameName > 0)) { + // 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) + || (groupsWithSameName <= 0); + } + 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.getValue()) { + return true; + } + + if (StringUtil.isNullOrEmpty(input)) { + return false; + } + + try { + Pattern.compile(input); + return true; + } catch (PatternSyntaxException ignored) { + 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, + 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 -> !StringUtil.isNullOrEmpty(input), + ValidationMessage.error( + "%s > %n %s" + .formatted( + Localization.lang("Free search expression"), + Localization.lang("Search term is empty.")))); + + 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( + (obs, _oldValue, isSelected) -> { + if (isSelected) { + validator.addValidators(searchSearchTermEmptyValidator); + } else { + validator.removeValidators(searchSearchTermEmptyValidator); } - - if (StringUtil.isNullOrEmpty(input)) { - return false; + }); + + typeKeywordsProperty.addListener( + (obs, _oldValue, isSelected) -> { + if (isSelected) { + validator.addValidators( + keywordFieldEmptyValidator, + keywordRegexValidator, + keywordSearchTermEmptyValidator); + } else { + validator.removeValidators( + keywordFieldEmptyValidator, + keywordRegexValidator, + keywordSearchTermEmptyValidator); } + }); - try { - Pattern.compile(input); - return true; - } catch (PatternSyntaxException ignored) { - 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, - 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 -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %n %s".formatted( - Localization.lang("Free search expression"), - Localization.lang("Search term is empty.")))); - - texGroupFilePathValidator = new FunctionBasedValidator<>( - texGroupFilePathProperty, - input -> { - if (StringUtil.isBlank(input)) { - return false; + typeTexProperty.addListener( + (obs, oldValue, isSelected) -> { + if (isSelected) { + validator.addValidators(texGroupFilePathValidator); } else { - Path inputPath = getAbsoluteTexGroupPath(input); - if (!inputPath.isAbsolute() || !Files.isRegularFile(inputPath)) { - return false; - } - return FileUtil.getFileExtension(input) - .map("aux"::equalsIgnoreCase) - .orElse(false); + validator.removeValidators(texGroupFilePathValidator); } - }, - ValidationMessage.error(Localization.lang("Please provide a valid aux file."))); - - typeSearchProperty.addListener((obs, _oldValue, isSelected) -> { - if (isSelected) { - validator.addValidators(searchSearchTermEmptyValidator); - } else { - validator.removeValidators(searchSearchTermEmptyValidator); - } - }); - - typeKeywordsProperty.addListener((obs, _oldValue, isSelected) -> { - if (isSelected) { - validator.addValidators(keywordFieldEmptyValidator, keywordRegexValidator, keywordSearchTermEmptyValidator); - } else { - validator.removeValidators(keywordFieldEmptyValidator, keywordRegexValidator, keywordSearchTermEmptyValidator); - } - }); - - typeTexProperty.addListener((obs, oldValue, isSelected) -> { - if (isSelected) { - validator.addValidators(texGroupFilePathValidator); - } else { - validator.removeValidators(texGroupFilePathValidator); - } - }); + }); - validator.addValidators(nameValidator, - nameContainsDelimiterValidator, - sameNameValidator); + validator.addValidators(nameValidator, nameContainsDelimiterValidator, sameNameValidator); } /** @@ -269,14 +312,18 @@ private void setupValidation() { * @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)); } public void validationHandler(Event event) { ValidationStatus validationStatus = validator.getValidationStatus(); if (validationStatus.getHighestMessage().isPresent()) { - dialogService.showErrorDialogAndWait(validationStatus.getHighestMessage().get().getMessage()); + dialogService.showErrorDialogAndWait( + validationStatus.getHighestMessage().get().getMessage()); // consume the event to prevent the dialog to close event.consume(); } @@ -291,50 +338,64 @@ public AbstractGroup resultConverter(ButtonType button) { try { String groupName = nameProperty.getValue().trim(); if (typeExplicitProperty.getValue()) { - resultingGroup = new ExplicitGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - preferences.getBibEntryPreferences().getKeywordSeparator()); + resultingGroup = + new ExplicitGroup( + groupName, + groupHierarchySelectedProperty.getValue(), + preferences.getBibEntryPreferences().getKeywordSeparator()); } else if (typeKeywordsProperty.getValue()) { if (keywordGroupRegexProperty.getValue()) { - resultingGroup = new RegexKeywordGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - FieldFactory.parseField(keywordGroupSearchFieldProperty.getValue().trim()), - keywordGroupSearchTermProperty.getValue().trim(), - keywordGroupCaseSensitiveProperty.getValue()); + resultingGroup = + new RegexKeywordGroup( + groupName, + groupHierarchySelectedProperty.getValue(), + FieldFactory.parseField( + keywordGroupSearchFieldProperty.getValue().trim()), + keywordGroupSearchTermProperty.getValue().trim(), + keywordGroupCaseSensitiveProperty.getValue()); } else { - resultingGroup = new WordKeywordGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - FieldFactory.parseField(keywordGroupSearchFieldProperty.getValue().trim()), - keywordGroupSearchTermProperty.getValue().trim(), - keywordGroupCaseSensitiveProperty.getValue(), - preferences.getBibEntryPreferences().getKeywordSeparator(), - false); + resultingGroup = + new WordKeywordGroup( + groupName, + groupHierarchySelectedProperty.getValue(), + FieldFactory.parseField( + keywordGroupSearchFieldProperty.getValue().trim()), + keywordGroupSearchTermProperty.getValue().trim(), + keywordGroupCaseSensitiveProperty.getValue(), + preferences.getBibEntryPreferences().getKeywordSeparator(), + false); } } else if (typeSearchProperty.getValue()) { - resultingGroup = new SearchGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - searchGroupSearchTermProperty.getValue().trim(), - searchFlagsProperty.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 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. + // 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); + currentDatabase + .getMetaData() + .setGroupSearchSyntaxVersion( + SearchGroupsMigrationAction.VERSION_6_0_ALPHA); } } - Optional luceneManager = stateManager.getLuceneManager(currentDatabase); + Optional luceneManager = + stateManager.getLuceneManager(currentDatabase); if (luceneManager.isPresent()) { SearchGroup searchGroup = (SearchGroup) resultingGroup; - searchGroup.setMatchedEntries(luceneManager.get().search(searchGroup.getQuery()).getMatchedEntries()); + searchGroup.setMatchedEntries( + luceneManager.get().search(searchGroup.getQuery()).getMatchedEntries()); } } else if (typeAutoProperty.getValue()) { if (autoGroupKeywordsOptionProperty.getValue()) { @@ -347,34 +408,43 @@ public AbstractGroup resultConverter(ButtonType button) { delimiter = autoGroupKeywordsDelimiterProperty.getValue().charAt(0); } if (!autoGroupKeywordsHierarchicalDelimiterProperty.getValue().isEmpty()) { - hierarDelimiter = autoGroupKeywordsHierarchicalDelimiterProperty.getValue().charAt(0); + hierarDelimiter = + autoGroupKeywordsHierarchicalDelimiterProperty.getValue().charAt(0); } - resultingGroup = new AutomaticKeywordGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - FieldFactory.parseField(autoGroupKeywordsFieldProperty.getValue().trim()), - delimiter, - hierarDelimiter); + resultingGroup = + new AutomaticKeywordGroup( + groupName, + groupHierarchySelectedProperty.getValue(), + FieldFactory.parseField( + autoGroupKeywordsFieldProperty.getValue().trim()), + delimiter, + hierarDelimiter); } else { - resultingGroup = new AutomaticPersonsGroup( - groupName, - groupHierarchySelectedProperty.getValue(), - FieldFactory.parseField(autoGroupPersonsFieldProperty.getValue().trim())); + resultingGroup = + new AutomaticPersonsGroup( + groupName, + groupHierarchySelectedProperty.getValue(), + FieldFactory.parseField( + autoGroupPersonsFieldProperty.getValue().trim())); } } else if (typeTexProperty.getValue()) { - resultingGroup = TexGroup.create( - groupName, - groupHierarchySelectedProperty.getValue(), - Path.of(texGroupFilePathProperty.getValue().trim()), - new DefaultAuxParser(new BibDatabase()), - fileUpdateMonitor, - currentDatabase.getMetaData()); + 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(colorUseProperty.getValue() ? colorProperty.getValue() : null); + resultingGroup.setColor( + colorUseProperty.getValue() ? colorProperty.getValue() : null); resultingGroup.setDescription(descriptionProperty.getValue()); resultingGroup.setIconName(iconProperty.getValue()); return resultingGroup; @@ -388,23 +458,34 @@ public AbstractGroup resultConverter(ButtonType button) { } public void setValues() { - groupHierarchyListProperty.setValue(FXCollections.observableArrayList(GroupHierarchyType.values())); + groupHierarchyListProperty.setValue( + FXCollections.observableArrayList(GroupHierarchyType.values())); 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 -> !iconName.equals(DefaultGroupsFactory.ALL_ENTRIES_GROUP_DEFAULT_ICON)) - .ifPresent(iconProperty::setValue); - parentNode.getGroup().getColor().ifPresent(color -> colorUseProperty.setValue(true)); + parentNode + .getGroup() + .getIconName() + .filter( + iconName -> + !iconName.equals( + DefaultGroupsFactory + .ALL_ENTRIES_GROUP_DEFAULT_ICON)) + .ifPresent(iconProperty::setValue); + parentNode + .getGroup() + .getColor() + .ifPresent(color -> colorUseProperty.setValue(true)); } typeExplicitProperty.setValue(true); - groupHierarchySelectedProperty.setValue(preferences.getGroupsPreferences().getDefaultHierarchicalContext()); + groupHierarchySelectedProperty.setValue( + preferences.getGroupsPreferences().getDefaultHierarchicalContext()); autoGroupKeywordsOptionProperty.setValue(Boolean.TRUE); } else { nameProperty.setValue(editedGroup.getName()); @@ -444,8 +525,10 @@ public void setValues() { if (editedGroup.getClass() == AutomaticKeywordGroup.class) { AutomaticKeywordGroup group = (AutomaticKeywordGroup) editedGroup; autoGroupKeywordsOptionProperty.setValue(Boolean.TRUE); - autoGroupKeywordsDelimiterProperty.setValue(group.getKeywordDelimiter().toString()); - autoGroupKeywordsHierarchicalDelimiterProperty.setValue(group.getKeywordHierarchicalDelimiter().toString()); + autoGroupKeywordsDelimiterProperty.setValue( + group.getKeywordDelimiter().toString()); + autoGroupKeywordsHierarchicalDelimiterProperty.setValue( + group.getKeywordHierarchicalDelimiter().toString()); autoGroupKeywordsFieldProperty.setValue(group.getField().getName()); } else if (editedGroup.getClass() == AutomaticPersonsGroup.class) { AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup; @@ -466,33 +549,53 @@ private Color determineColor() { if (parentNode == null) { color = GroupColorPicker.generateColor(List.of()); } else { - 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 = parentNode.getGroup().getColor(); - color = parentColor.map(value -> GroupColorPicker.generateColor(colorsOfSiblings, value)) - .orElseGet(() -> GroupColorPicker.generateColor(colorsOfSiblings)); + color = + parentColor + .map(value -> GroupColorPicker.generateColor(colorsOfSiblings, value)) + .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(); - dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(file -> texGroupFilePathProperty.setValue( - FileUtil.relativize(file.toAbsolutePath(), getFileDirectoriesAsPaths()).toString() - )); + 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(); + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + file -> + texGroupFilePathProperty.setValue( + FileUtil.relativize( + file.toAbsolutePath(), + getFileDirectoriesAsPaths()) + .toString())); } private List getFileDirectoriesAsPaths() { List fileDirs = new ArrayList<>(); MetaData metaData = currentDatabase.getMetaData(); - metaData.getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()).ifPresent(fileDirs::add); + metaData.getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()) + .ifPresent(fileDirs::add); return fileDirs; } diff --git a/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java index 6813de6227e1..f2d53be31fce 100644 --- a/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupModeViewModel.java @@ -1,13 +1,13 @@ package org.jabref.gui.groups; -import java.util.Set; - import javafx.scene.Node; import javafx.scene.control.Tooltip; import org.jabref.gui.icon.IconTheme.JabRefIcons; import org.jabref.logic.l10n.Localization; +import java.util.Set; + public class GroupModeViewModel { private final Set mode; diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index cc64665235f4..0eadaf174f51 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -1,9 +1,10 @@ package org.jabref.gui.groups; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; +import com.google.common.eventbus.Subscribe; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyObservableList; + +import io.github.adr.linked.ADR; import javafx.beans.InvalidationListener; import javafx.beans.WeakInvalidationListener; @@ -52,10 +53,10 @@ import org.jabref.model.search.event.IndexStartedEvent; import org.jabref.model.strings.StringUtil; -import com.google.common.eventbus.Subscribe; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.EasyObservableList; -import io.github.adr.linked.ADR; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; public class GroupNodeViewModel { @@ -65,8 +66,10 @@ public class GroupNodeViewModel { 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; @@ -74,12 +77,20 @@ public class GroupNodeViewModel { private final TaskExecutor taskExecutor; private final CustomLocalDragboard localDragBoard; private final GuiPreferences preferences; + @SuppressWarnings("FieldCanBeLocal") private final ObservableList entriesList; + @SuppressWarnings("FieldCanBeLocal") private final InvalidationListener onInvalidatedGroup = listener -> 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); @@ -90,56 +101,111 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state displayName = new LatexToUnicodeFormatter().format(groupNode.getName()); 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)); + 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 { children = EasyBind.mapBacked(groupNode.getChildren(), this::toViewModel); } if (groupNode.getGroup() instanceof TexGroup) { - databaseContext.getMetaData().groupsBinding().addListener(new WeakInvalidationListener(onInvalidatedGroup)); + databaseContext + .getMetaData() + .groupsBinding() + .addListener(new WeakInvalidationListener(onInvalidatedGroup)); } else if (groupNode.getGroup() instanceof SearchGroup searchGroup) { - stateManager.getLuceneManager(databaseContext).ifPresent(luceneManager -> { - BackgroundTask.wrap(() -> { - searchGroup.setMatchedEntries(luceneManager.search(searchGroup.getQuery()).getMatchedEntries()); - }).onSuccess(success -> { - refreshGroup(); - databaseContext.getMetaData().groupsBinding().invalidate(); - }).executeWith(taskExecutor); - }); + stateManager + .getLuceneManager(databaseContext) + .ifPresent( + luceneManager -> { + BackgroundTask.wrap( + () -> { + searchGroup.setMatchedEntries( + luceneManager + .search(searchGroup.getQuery()) + .getMatchedEntries()); + }) + .onSuccess( + success -> { + refreshGroup(); + databaseContext + .getMetaData() + .groupsBinding() + .invalidate(); + }) + .executeWith(taskExecutor); + }); } hasChildren = new SimpleBooleanProperty(); hasChildren.bind(Bindings.isNotEmpty(children)); - EasyBind.subscribe(preferences.getGroupsPreferences().displayGroupCountProperty(), shouldDisplay -> updateMatchedEntries()); + EasyBind.subscribe( + preferences.getGroupsPreferences().displayGroupCountProperty(), + shouldDisplay -> updateMatchedEntries()); expandedProperty.set(groupNode.getGroup().isExpanded()); - expandedProperty.addListener((observable, oldValue, newValue) -> groupNode.getGroup().setExpanded(newValue)); + expandedProperty.addListener( + (observable, oldValue, 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 LuceneIndexListener()); } - 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); + 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); } private GroupNodeViewModel toViewModel(GroupTreeNode child) { - return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, child, localDragBoard, preferences); + return new GroupNodeViewModel( + databaseContext, stateManager, taskExecutor, child, localDragBoard, preferences); } public List addEntriesToGroup(List entries) { @@ -158,7 +224,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() { @@ -209,15 +276,24 @@ 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 @@ -227,8 +303,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() { @@ -261,7 +336,8 @@ private void onDatabaseChanged(ListChangeListener.Change cha if (change.wasPermutated()) { // Nothing to do, as permutation doesn't change matched entries } else if (change.wasUpdated()) { - for (BibEntry changedEntry : change.getList().subList(change.getFrom(), change.getTo())) { + for (BibEntry changedEntry : + change.getList().subList(change.getFrom(), change.getTo())) { if (groupNode.matches(changedEntry)) { // ADR-0038 matchedEntries.add(changedEntry.getId()); @@ -286,28 +362,32 @@ 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 - ObservableList selectedGroups = this.stateManager.getSelectedGroups(this.databaseContext); - if (selectedGroups.remove(this.groupNode)) { - selectedGroups.add(this.groupNode); - } - }); + 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 + ObservableList selectedGroups = + this.stateManager.getSelectedGroups(this.databaseContext); + if (selectedGroups.remove(this.groupNode)) { + selectedGroups.add(this.groupNode); + } + }); } 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())); - }) + BackgroundTask.wrap(() -> groupNode.findMatches(databaseContext.getDatabase())) + .onSuccess( + entries -> { + matchedEntries.clear(); + // ADR-0038 + entries.forEach(entry -> matchedEntries.add(entry.getId())); + }) .executeWith(taskExecutor); } } @@ -321,7 +401,8 @@ void toggleExpansion() { } boolean isMatchedBy(String searchString) { - return StringUtil.isBlank(searchString) || StringUtil.containsIgnoreCase(getDisplayName(), searchString); + return StringUtil.isBlank(searchString) + || StringUtil.containsIgnoreCase(getDisplayName(), searchString); } public Color getColor() { @@ -346,19 +427,23 @@ public Optional getChildByPath(String pathToSource) { public boolean acceptableDrop(Dragboard dragboard) { // TODO: we should also check isNodeDescendant boolean canDropOtherGroup = dragboard.hasContent(DragAndDropDataFormats.GROUP); - boolean canDropEntries = localDragBoard.hasBibEntries() && (groupNode.getGroup() instanceof GroupEntryChanger); + boolean canDropEntries = + localDragBoard.hasBibEntries() + && (groupNode.getGroup() instanceof GroupEntryChanger); return canDropOtherGroup || canDropEntries; } public void moveTo(GroupNodeViewModel target) { // TODO: Add undo and display message - // MoveGroupChange undo = new MoveGroupChange(((GroupTreeNodeViewModel)source.getParent()).getNode(), + // 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.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) { @@ -416,10 +501,14 @@ public boolean canAddEntriesIn() { } else if (group instanceof ExplicitGroup) { return true; } else if (group instanceof LastNameGroup || group instanceof RegexKeywordGroup) { - return groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup) - .orElse(false); + return groupNode + .getParent() + .map(GroupTreeNode::getGroup) + .map( + groupParent -> + groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup) + .orElse(false); } else if (group instanceof KeywordGroup) { // also covers WordKeywordGroup return true; @@ -432,7 +521,9 @@ public boolean canAddEntriesIn() { } else if (group instanceof TexGroup) { return false; } else { - throw new UnsupportedOperationException("canAddEntriesIn method not yet implemented in group: " + group.getClass().getName()); + throw new UnsupportedOperationException( + "canAddEntriesIn method not yet implemented in group: " + + group.getClass().getName()); } } @@ -444,10 +535,14 @@ public boolean canBeDragged() { return true; } else if (group instanceof KeywordGroup) { // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - return groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + return groupNode + .getParent() + .map(GroupTreeNode::getGroup) + .map( + groupParent -> + !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); } else if (group instanceof SearchGroup) { return true; } else if (group instanceof AutomaticKeywordGroup) { @@ -457,7 +552,9 @@ public boolean canBeDragged() { } else if (group instanceof TexGroup) { return true; } else { - throw new UnsupportedOperationException("canBeDragged method not yet implemented in group: " + group.getClass().getName()); + throw new UnsupportedOperationException( + "canBeDragged method not yet implemented in group: " + + group.getClass().getName()); } } @@ -469,10 +566,14 @@ public boolean canAddGroupsIn() { return true; } else if (group instanceof KeywordGroup) { // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - return groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + return groupNode + .getParent() + .map(GroupTreeNode::getGroup) + .map( + groupParent -> + !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); } else if (group instanceof SearchGroup) { return true; } else if (group instanceof AutomaticKeywordGroup) { @@ -482,7 +583,9 @@ public boolean canAddGroupsIn() { } else if (group instanceof TexGroup) { return true; } else { - throw new UnsupportedOperationException("canAddGroupsIn method not yet implemented in group: " + group.getClass().getName()); + throw new UnsupportedOperationException( + "canAddGroupsIn method not yet implemented in group: " + + group.getClass().getName()); } } @@ -494,10 +597,14 @@ public boolean canRemove() { return true; } else if (group instanceof KeywordGroup) { // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - return groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + return groupNode + .getParent() + .map(GroupTreeNode::getGroup) + .map( + groupParent -> + !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); } else if (group instanceof SearchGroup) { return true; } else if (group instanceof AutomaticKeywordGroup) { @@ -507,7 +614,8 @@ public boolean canRemove() { } else if (group instanceof TexGroup) { return true; } else { - throw new UnsupportedOperationException("canRemove method not yet implemented in group: " + group.getClass().getName()); + throw new UnsupportedOperationException( + "canRemove method not yet implemented in group: " + group.getClass().getName()); } } @@ -519,10 +627,14 @@ public boolean isEditable() { return true; } else if (group instanceof KeywordGroup) { // KeywordGroup is parent of LastNameGroup, RegexKeywordGroup and WordKeywordGroup - return groupNode.getParent() - .map(GroupTreeNode::getGroup) - .map(groupParent -> !(groupParent instanceof AutomaticKeywordGroup || groupParent instanceof AutomaticPersonsGroup)) - .orElse(false); + return groupNode + .getParent() + .map(GroupTreeNode::getGroup) + .map( + groupParent -> + !(groupParent instanceof AutomaticKeywordGroup + || groupParent instanceof AutomaticPersonsGroup)) + .orElse(false); } else if (group instanceof SearchGroup) { return true; } else if (group instanceof AutomaticKeywordGroup) { @@ -532,7 +644,9 @@ public boolean isEditable() { } else if (group instanceof TexGroup) { return true; } else { - throw new UnsupportedOperationException("isEditable method not yet implemented in group: " + group.getClass().getName()); + throw new UnsupportedOperationException( + "isEditable method not yet implemented in group: " + + group.getClass().getName()); } } @@ -540,35 +654,63 @@ class LuceneIndexListener { @Subscribe public void listen(IndexStartedEvent event) { if (groupNode.getGroup() instanceof SearchGroup searchGroup) { - stateManager.getLuceneManager(databaseContext).ifPresent(luceneManager -> { - BackgroundTask.wrap(() -> { - searchGroup.setMatchedEntries(luceneManager.search(searchGroup.getQuery()).getMatchedEntries()); - }).onSuccess(success -> { - refreshGroup(); - databaseContext.getMetaData().groupsBinding().invalidate(); - }).executeWith(taskExecutor); - }); + stateManager + .getLuceneManager(databaseContext) + .ifPresent( + luceneManager -> { + BackgroundTask.wrap( + () -> { + searchGroup.setMatchedEntries( + luceneManager + .search( + searchGroup + .getQuery()) + .getMatchedEntries()); + }) + .onSuccess( + success -> { + refreshGroup(); + databaseContext + .getMetaData() + .groupsBinding() + .invalidate(); + }) + .executeWith(taskExecutor); + }); } } @Subscribe public void listen(IndexAddedOrUpdatedEvent event) { if (groupNode.getGroup() instanceof SearchGroup searchGroup) { - stateManager.getLuceneManager(databaseContext).ifPresent(luceneManager -> { - BackgroundTask.wrap(() -> { - for (BibEntry entry : event.entries()) { - searchGroup.updateMatches(entry, luceneManager.isEntryMatched(entry, searchGroup.getQuery())); - } - }).onFinished(() -> { - for (BibEntry entry : event.entries()) { - if (groupNode.matches(entry)) { - matchedEntries.add(entry.getId()); - } else { - matchedEntries.remove(entry.getId()); - } - } - }).executeWith(taskExecutor); - }); + stateManager + .getLuceneManager(databaseContext) + .ifPresent( + luceneManager -> { + BackgroundTask.wrap( + () -> { + for (BibEntry entry : event.entries()) { + searchGroup.updateMatches( + entry, + luceneManager.isEntryMatched( + entry, + searchGroup + .getQuery())); + } + }) + .onFinished( + () -> { + for (BibEntry entry : event.entries()) { + if (groupNode.matches(entry)) { + matchedEntries.add(entry.getId()); + } else { + matchedEntries.remove( + entry.getId()); + } + } + }) + .executeWith(taskExecutor); + }); } } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java index 38f9c532988c..1153f80340e7 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeNodeViewModel.java @@ -1,12 +1,5 @@ package org.jabref.gui.groups; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import javax.swing.undo.AbstractUndoableEdit; -import javax.swing.undo.UndoManager; - import org.jabref.gui.undo.CountingUndoManager; import org.jabref.model.FieldChange; import org.jabref.model.entry.BibEntry; @@ -18,6 +11,13 @@ import org.jabref.model.groups.KeywordGroup; import org.jabref.model.groups.SearchGroup; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.UndoManager; + public class GroupTreeNodeViewModel { private final GroupTreeNode node; @@ -50,32 +50,38 @@ public String getDescription() { AbstractGroup group = node.getGroup(); String shortDescription = ""; boolean showDynamic = true; - shortDescription = switch (group) { - 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(); - }; + shortDescription = + switch (group) { + 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(); + }; return "" + shortDescription + ""; } public boolean canAddEntries(List entries) { - return (getNode().getGroup() instanceof GroupEntryChanger) && !getNode().getGroup().containsAll(entries); + return (getNode().getGroup() instanceof GroupEntryChanger) + && !getNode().getGroup().containsAll(entries); } public boolean canRemoveEntries(List entries) { - return (getNode().getGroup() instanceof GroupEntryChanger) && getNode().getGroup().containsAny(entries); + return (getNode().getGroup() instanceof GroupEntryChanger) + && getNode().getGroup().containsAny(entries); } public void sortChildrenByName(boolean recursive) { - getNode().sortChildren( - (node1, node2) -> node1.getGroup().getName().compareToIgnoreCase(node2.getGroup().getName()), - recursive); + getNode() + .sortChildren( + (node1, node2) -> + node1.getGroup() + .getName() + .compareToIgnoreCase(node2.getGroup().getName()), + recursive); } @Override @@ -156,7 +162,8 @@ public void changeEntriesTo(List entries, UndoManager undoManager) { // Remember undo information if (!changesRemove.isEmpty()) { - AbstractUndoableEdit undoRemove = UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesRemove); + AbstractUndoableEdit undoRemove = + UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesRemove); if (!changesAdd.isEmpty() && (undoRemove != null)) { // we removed and added entries undoRemove.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(this, changesAdd)); @@ -179,10 +186,11 @@ 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.ADD_NODE); + UndoableAddOrRemoveGroup undo = + new UndoableAddOrRemoveGroup( + this, + new GroupTreeNodeViewModel(newNode), + UndoableAddOrRemoveGroup.ADD_NODE); undoManager.addEdit(undo); } @@ -194,6 +202,8 @@ public List addEntriesToGroup(List entries) { } public void subscribeToDescendantChanged(Consumer subscriber) { - getNode().subscribeToDescendantChanged(node -> subscriber.accept(new GroupTreeNodeViewModel(node))); + getNode() + .subscribeToDescendantChanged( + node -> subscriber.accept(new GroupTreeNodeViewModel(node))); } } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeView.java b/src/main/java/org/jabref/gui/groups/GroupTreeView.java index a28919ef26e2..9c06fc9a4e89 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeView.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeView.java @@ -1,15 +1,6 @@ package org.jabref.gui.groups; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.text.DecimalFormat; -import java.time.Duration; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; +import com.tobiasdiez.easybind.EasyBind; import javafx.application.Platform; import javafx.beans.binding.Bindings; @@ -43,6 +34,8 @@ import javafx.scene.layout.StackPane; import javafx.scene.text.Text; +import org.controlsfx.control.textfield.CustomTextField; +import org.controlsfx.control.textfield.TextFields; import org.jabref.architecture.AllowedToUseClassGetResource; import org.jabref.gui.DialogService; import org.jabref.gui.DragAndDropDataFormats; @@ -62,23 +55,33 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.BibEntry; - -import com.tobiasdiez.easybind.EasyBind; -import org.controlsfx.control.textfield.CustomTextField; -import org.controlsfx.control.textfield.TextFields; import org.reactfx.util.FxTimer; import org.reactfx.util.Timer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.DecimalFormat; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") 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_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; @@ -104,12 +107,12 @@ public class GroupTreeView extends BorderPane { /** * Note: This panel is deliberately not created in fxml, since parsing equivalent fxml takes about 500 msecs */ - public GroupTreeView(TaskExecutor taskExecutor, - StateManager stateManager, - GuiPreferences preferences, - DialogService dialogService, - ChatHistoryService chatHistoryService - ) { + public GroupTreeView( + TaskExecutor taskExecutor, + StateManager stateManager, + GuiPreferences preferences, + DialogService dialogService, + ChatHistoryService chatHistoryService) { this.taskExecutor = taskExecutor; this.stateManager = stateManager; this.preferences = preferences; @@ -117,7 +120,10 @@ public GroupTreeView(TaskExecutor taskExecutor, this.chatHistoryService = chatHistoryService; createNodes(); - this.getStylesheets().add(Objects.requireNonNull(GroupTreeView.class.getResource("GroupTree.css")).toExternalForm()); + this.getStylesheets() + .add( + Objects.requireNonNull(GroupTreeView.class.getResource("GroupTree.css")) + .toExternalForm()); initialize(); } @@ -164,45 +170,63 @@ private void createNodes() { private void initialize() { this.localDragboard = stateManager.getLocalDragboard(); - viewModel = new GroupTreeViewModel(stateManager, dialogService, chatHistoryService, preferences, taskExecutor, localDragboard); + viewModel = + new GroupTreeViewModel( + stateManager, + dialogService, + chatHistoryService, + preferences, + 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()); - viewModel.filterTextProperty().setValue(searchField.textProperty().getValue()); - }); - 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()); - } - })); + final Timer searchTask = + FxTimer.create( + Duration.ofMillis(400), + () -> { + LOGGER.debug("Run group search {}", searchField.getText()); + viewModel + .filterTextProperty() + .setValue(searchField.textProperty().getValue()); + }); + 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()); + } + })); // Icon and group name new ViewModelTreeTableCellFactory() @@ -219,38 +243,66 @@ private void initialize() { // Arrow indicating expanded status new ViewModelTreeTableCellFactory() .withGraphic(this::getArrowCell) - .withOnMouseClickedEvent(group -> event -> { - group.toggleExpansion(); - event.consume(); - }) + .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(); - } 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)); - }) + .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(); + } 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())) + .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(); @@ -274,13 +326,14 @@ private StackPane createNumberCell(GroupNodeViewModel group) { final StackPane node = new StackPane(); node.getStyleClass().add("hits"); if (!group.isRoot()) { - BindingsHelper.includePseudoClassWhen(node, PSEUDOCLASS_ANYSELECTED, - group.anySelectedEntriesMatchedProperty()); - BindingsHelper.includePseudoClassWhen(node, PSEUDOCLASS_ALLSELECTED, - group.allSelectedEntriesMatchedProperty()); + BindingsHelper.includePseudoClassWhen( + node, PSEUDOCLASS_ANYSELECTED, group.anySelectedEntriesMatchedProperty()); + BindingsHelper.includePseudoClassWhen( + node, PSEUDOCLASS_ALLSELECTED, group.allSelectedEntriesMatchedProperty()); } Text text = new Text(); - EasyBind.subscribe(preferences.getGroupsPreferences().displayGroupCountProperty(), + EasyBind.subscribe( + preferences.getGroupsPreferences().displayGroupCountProperty(), shouldDisplayGroupCount -> { if (text.textProperty().isBound()) { text.textProperty().unbind(); @@ -288,7 +341,11 @@ private StackPane createNumberCell(GroupNodeViewModel group) { } if (shouldDisplayGroupCount) { - text.textProperty().bind(group.getHits().map(Number::intValue).map(this::getFormattedNumber)); + 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); @@ -296,34 +353,47 @@ private StackPane createNumberCell(GroupNodeViewModel group) { }); text.getStyleClass().setAll("text"); - text.styleProperty().bind(Bindings.createStringBinding(() -> { - double reducedFontSize; - double font_size = preferences.getWorkspacePreferences().getMainFontSize(); - // 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) { - reducedFontSize = 0.35; - } else if (font_size > 18.0) { - reducedFontSize = 0.55; - } else { - reducedFontSize = 0.75; - } - return "-fx-font-size: %fem;".formatted(reducedFontSize); - }, preferences.getWorkspacePreferences().mainFontSizeProperty())); + text.styleProperty() + .bind( + Bindings.createStringBinding( + () -> { + double reducedFontSize; + double font_size = + preferences.getWorkspacePreferences().getMainFontSize(); + // 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) { + reducedFontSize = 0.35; + } else if (font_size > 18.0) { + reducedFontSize = 0.55; + } else { + reducedFontSize = 0.75; + } + return "-fx-font-size: %fem;".formatted(reducedFontSize); + }, + preferences.getWorkspacePreferences().mainFontSizeProperty())); node.getChildren().add(text); node.setMaxWidth(Control.USE_PREF_SIZE); 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()); } @@ -343,19 +413,25 @@ 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; if (dragboard.hasContent(DragAndDropDataFormats.GROUP) && row.getItem().canAddGroupsIn()) { - List pathToSources = (List) dragboard.getContent(DragAndDropDataFormats.GROUP); + 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)); + source.get() + .draggedOn( + row.getItem(), + ControlHelper.getDroppingMouseLocation(row, event)); changedGroups.add(source.get()); success = true; } @@ -376,9 +452,14 @@ private void handleOnDragDropped(TreeTableRow row, GroupNode 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)) { + if ((event.getGestureSource() != row) + && (row.getItem() != null) + && row.getItem().acceptableDrop(dragboard)) { event.acceptTransferModes(TransferMode.MOVE, TransferMode.LINK); // expand node and all children on drag over @@ -396,7 +477,11 @@ private void updateSelection(List> newSelectedGroup 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()); + List list = + newSelectedGroups.stream() + .filter(model -> (model != null) && (model.getValue() != null)) + .map(TreeItem::getValue) + .collect(Collectors.toList()); viewModel.selectedGroupsProperty().setAll(list); } } @@ -407,24 +492,25 @@ 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); - }); + .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) { return getTreeItemByValue(groupTree.getRoot(), value); } - private Optional> getTreeItemByValue(TreeItem root, - GroupNodeViewModel value) { + private Optional> getTreeItemByValue( + TreeItem root, GroupNodeViewModel value) { if (root == null) { return Optional.empty(); } @@ -445,47 +531,60 @@ private Optional> getTreeItemByValue(TreeItem - getVerticalScrollbar().ifPresent(scrollBar -> { - double newValue = scrollBar.getValue() + scrollVelocity; - newValue = Math.min(newValue, 1d); - newValue = Math.max(newValue, 0d); - scrollBar.setValue(newValue); - })); + scrollTimer = + FxTimer.createPeriodic( + Duration.ofMillis(100), + () -> + getVerticalScrollbar() + .ifPresent( + scrollBar -> { + double newValue = + scrollBar.getValue() + scrollVelocity; + newValue = Math.min(newValue, 1d); + newValue = Math.max(newValue, 0d); + scrollBar.setValue(newValue); + })); // Start - groupTree.setOnDragEntered(event -> { - initScrolling(); - scrollTimer.restart(); - }); + groupTree.setOnDragEntered( + event -> { + initScrolling(); + scrollTimer.restart(); + }); // During dragging - groupTree.setOnDragOver(event -> { - boolean scrollingUp = event.getY() < upperBorder; - boolean scrollingDown = event.getY() > lowerBorder; - - if (!scrollingUp && !scrollingDown) { - scrollVelocity = 0; - return; - } + groupTree.setOnDragOver( + event -> { + boolean scrollingUp = event.getY() < upperBorder; + boolean scrollingDown = event.getY() > lowerBorder; + + if (!scrollingUp && !scrollingDown) { + scrollVelocity = 0; + return; + } - double distanceFromNonScrollableInsideArea; - if (scrollingUp) { - distanceFromNonScrollableInsideArea = scrollableAreaHeight - event.getY(); - } else { - distanceFromNonScrollableInsideArea = scrollableAreaHeight - (groupTree.getHeight() - event.getY()); - } + double distanceFromNonScrollableInsideArea; + if (scrollingUp) { + distanceFromNonScrollableInsideArea = scrollableAreaHeight - event.getY(); + } 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) - scrollVelocity = (baseFactor * (1.0 + distanceFromNonScrollableInsideArea)) / 10.0; - if (scrollingUp) { - scrollVelocity = -scrollVelocity; - } - }); + // 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; + } + }); // Stop groupTree.setOnScroll(event -> scrollTimer.stop()); @@ -502,10 +601,12 @@ private void initScrolling() { return; } - double heightOfOneNode = groupTree.getChildrenUnmodifiable().getFirst().getLayoutBounds().getHeight(); + 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 @@ -513,8 +614,10 @@ 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); } @@ -539,34 +642,71 @@ private ContextMenu createContextMenuForGroup(GroupNodeViewModel group) { MenuItem removeGroup; if (group.hasSubgroups() && group.canAddGroupsIn() && !group.isRoot()) { - removeGroup = new Menu(Localization.lang("Remove group"), null, - 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)) - ); + removeGroup = + new Menu( + Localization.lang("Remove group"), + null, + 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)); + 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)), - removeGroup, - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.GROUP_SUBGROUP_ADD, new ContextAction(StandardActions.GROUP_SUBGROUP_ADD, 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_EDIT, + new ContextAction(StandardActions.GROUP_EDIT, group)), + removeGroup, + new SeparatorMenuItem(), + factory.createMenuItem( + StandardActions.GROUP_SUBGROUP_ADD, + new ContextAction(StandardActions.GROUP_SUBGROUP_ADD, 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"); @@ -592,7 +732,9 @@ private String getFormattedNumber(int hits) { // 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) { @@ -633,47 +775,47 @@ public ContextAction(StandardActions command, GroupNodeViewModel group) { this.command = command; this.group = group; - this.executable.bind(BindingsHelper.constantOf( - switch (command) { - case GROUP_EDIT -> - group.isEditable(); - case GROUP_REMOVE, GROUP_REMOVE_WITH_SUBGROUPS, GROUP_REMOVE_KEEP_SUBGROUPS -> - group.isEditable() && group.canRemove(); - 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.isEditable(); + case GROUP_REMOVE, + GROUP_REMOVE_WITH_SUBGROUPS, + GROUP_REMOVE_KEEP_SUBGROUPS -> + group.isEditable() && group.canRemove(); + 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_REMOVE -> viewModel.removeGroupNoSubgroups(group); + case GROUP_REMOVE_KEEP_SUBGROUPS -> viewModel.removeGroupKeepSubgroups(group); + case GROUP_REMOVE_WITH_SUBGROUPS -> viewModel.removeGroupAndSubgroups(group); case GROUP_EDIT -> { viewModel.editGroup(group); groupTree.refresh(); } - case GROUP_CHAT -> - viewModel.chatWithGroup(group); + case GROUP_CHAT -> viewModel.chatWithGroup(group); case GROUP_SUBGROUP_ADD -> viewModel.addNewSubgroup(group, GroupDialogHeader.SUBGROUP); - case GROUP_SUBGROUP_REMOVE -> - viewModel.removeSubgroups(group); + case GROUP_SUBGROUP_REMOVE -> viewModel.removeSubgroups(group); case GROUP_SUBGROUP_SORT -> viewModel.sortAlphabeticallyRecursive(group.getGroupNode()); case GROUP_SUBGROUP_SORT_REVERSE -> @@ -682,10 +824,8 @@ public void execute() { 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_ENTRIES_ADD -> viewModel.addSelectedEntries(group); + case GROUP_ENTRIES_REMOVE -> viewModel.removeSelectedEntries(group); } } } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 94e4b06aec3c..fb58d70b21db 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -1,13 +1,9 @@ package org.jabref.gui.groups; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; +import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; + +import dev.langchain4j.data.message.ChatMessage; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -44,41 +40,58 @@ import org.jabref.model.groups.WordKeywordGroup; import org.jabref.model.metadata.MetaData; -import com.airhacks.afterburner.injection.Injector; -import com.tobiasdiez.easybind.EasyBind; -import dev.langchain4j.data.message.ChatMessage; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; 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 ChatHistoryService chatHistoryService; private final GuiPreferences preferences; private final TaskExecutor taskExecutor; private final CustomLocalDragboard localDragboard; - private final ObjectProperty> filterPredicate = new SimpleObjectProperty<>(); + 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 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 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, ChatHistoryService chatHistoryService, GuiPreferences preferences, TaskExecutor taskExecutor, CustomLocalDragboard localDragboard) { + public GroupTreeViewModel( + StateManager stateManager, + DialogService dialogService, + ChatHistoryService chatHistoryService, + GuiPreferences preferences, + TaskExecutor taskExecutor, + CustomLocalDragboard localDragboard) { this.stateManager = Objects.requireNonNull(stateManager); this.dialogService = Objects.requireNonNull(dialogService); this.chatHistoryService = Objects.requireNonNull(chatHistoryService); @@ -124,13 +137,18 @@ private void onSelectedGroupChanged(ObservableList newValue) return; } - currentDatabase.ifPresent(database -> { - if ((newValue == null) || newValue.isEmpty()) { - stateManager.clearSelectedGroups(database); - } else { - stateManager.setSelectedGroups(database, newValue.stream().map(GroupNodeViewModel::getGroupNode).collect(Collectors.toList())); - } - }); + currentDatabase.ifPresent( + database -> { + if ((newValue == null) || newValue.isEmpty()) { + stateManager.clearSelectedGroups(database); + } else { + stateManager.setSelectedGroups( + database, + newValue.stream() + .map(GroupNodeViewModel::getGroupNode) + .collect(Collectors.toList())); + } + }); } /** @@ -140,7 +158,9 @@ 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.")); + dialogService.showWarningDialogAndWait( + Localization.lang("Cannot create group"), + Localization.lang("Cannot create group. Please create a library first.")); } } @@ -150,20 +170,44 @@ public void addNewGroupToRoot() { */ 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(), Collections.singletonList(newRoot.getGroupNode())); + stateManager.setSelectedGroups( + newDatabase.get(), Collections.singletonList(newRoot.getGroupNode())); } selectedGroups.setAll( stateManager.getSelectedGroups(newDatabase.get()).stream() - .map(selectedGroup -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, selectedGroup, localDragboard, preferences)) - .collect(Collectors.toList())); + .map( + selectedGroup -> + new GroupNodeViewModel( + newDatabase.get(), + stateManager, + taskExecutor, + selectedGroup, + localDragboard, + preferences)) + .collect(Collectors.toList())); } else { rootGroup.setValue(null); } @@ -174,31 +218,47 @@ private void onActiveDatabaseChanged(Optional newDatabase) { * 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)); - - newGroup.ifPresent(group -> { - GroupTreeNode newSubgroup = parent.addSubgroup(group); - selectedGroups.setAll(new GroupNodeViewModel(database, stateManager, taskExecutor, newSubgroup, localDragboard, preferences)); - - // TODO: Add undo - // UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(parent, new GroupTreeNodeViewModel(newGroupNode), UndoableAddOrRemoveGroup.ADD_NODE); - // panel.getUndoManager().addEdit(undo); - - // TODO: Expand parent to make new group visible - // parent.expand(); - dialogService.notify(Localization.lang("Added group \"%0\".", group.getName())); - writeGroupChangesToMetaData(); - }); - }); + currentDatabase.ifPresent( + database -> { + 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)); + + // TODO: Add undo + // UndoableAddOrRemoveGroup undo = new + // UndoableAddOrRemoveGroup(parent, new + // GroupTreeNodeViewModel(newGroupNode), + // UndoableAddOrRemoveGroup.ADD_NODE); + // panel.getUndoManager().addEdit(undo); + + // TODO: Expand parent to make new group visible + // parent.expand(); + dialogService.notify( + Localization.lang("Added group \"%0\".", group.getName())); + writeGroupChangesToMetaData(); + }); + }); } public void writeGroupChangesToMetaData() { - currentDatabase.ifPresent(database -> database.getMetaData().setGroups(rootGroup.get().getGroupNode())); + currentDatabase.ifPresent( + database -> database.getMetaData().setGroups(rootGroup.get().getGroupNode())); } private boolean isGroupTypeEqual(AbstractGroup oldGroup, AbstractGroup newGroup) { @@ -214,43 +274,68 @@ private boolean isGroupTypeEqual(AbstractGroup oldGroup, AbstractGroup newGroup) * @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()) - && Objects.equals(oldWordKeywordGroup.isCaseSensitive(), newWordKeywordGroup.isCaseSensitive()); + 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) { RegexKeywordGroup oldRegexKeywordGroup = (RegexKeywordGroup) oldGroup; RegexKeywordGroup newRegexKeywordGroup = (RegexKeywordGroup) newGroup; - return Objects.equals(oldRegexKeywordGroup.getSearchField().getName(), newRegexKeywordGroup.getSearchField().getName()) - && Objects.equals(oldRegexKeywordGroup.getSearchExpression(), newRegexKeywordGroup.getSearchExpression()) - && Objects.equals(oldRegexKeywordGroup.isCaseSensitive(), newRegexKeywordGroup.isCaseSensitive()); + 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) { SearchGroup oldSearchGroup = (SearchGroup) oldGroup; SearchGroup newSearchGroup = (SearchGroup) newGroup; - return Objects.equals(oldSearchGroup.getSearchExpression(), newSearchGroup.getSearchExpression()) - && Objects.equals(oldSearchGroup.getSearchFlags(), newSearchGroup.getSearchFlags()); + return Objects.equals( + oldSearchGroup.getSearchExpression(), + newSearchGroup.getSearchExpression()) + && Objects.equals( + oldSearchGroup.getSearchFlags(), newSearchGroup.getSearchFlags()); } 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()); + 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()); + 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()); + return Objects.equals( + oldTexGroup.getFilePath().toString(), newTexGroup.getFilePath().toString()); } return true; } @@ -259,136 +344,200 @@ boolean onlyMinorChanges(AbstractGroup oldGroup, AbstractGroup newGroup) { * Opens "Edit Group Dialog" and changes the given group to the edited one. */ 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)); - - newGroup.ifPresent(group -> { - AbstractGroup oldGroupDef = oldGroup.getGroupNode().getGroup(); - String oldGroupName = oldGroupDef.getName(); - - 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 - 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 found more than 2 groups, so we cannot simply remove old assignment - boolean removePreviousAssignments = groupsWithSameName < 2; - - 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 - refresh(); - return; - } - - if (groupTypeEqual && onlyMinorChanges(oldGroup.getGroupNode().getGroup(), group)) { - oldGroup.getGroupNode().setGroup( - group, - true, - true, - database.getEntries()); - - writeGroupChangesToMetaData(); - refresh(); - return; - } - - // Major modifications - - 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 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)"); - } - 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); - - int groupsWithSameName = 0; - 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(); - } - // okay we found more than 2 groups with the same name - // If we only found one we can still do it - if (groupsWithSameName >= 2) { - 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)) { - return; - } - - // stateManager.getEntriesInCurrentDatabase()); - - // TODO: Add undo - // Store undo information. - // AbstractUndoableEdit undoAddPreviousEntries = null; - // UndoableModifyGroup undo = new UndoableModifyGroup(GroupSelector.this, groupsRoot, node, newGroup); - // if (undoAddPreviousEntries == null) { - // panel.getUndoManager().addEdit(undo); - // } else { - // 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); - // } - - 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 - refresh(); - }); - }); + currentDatabase.ifPresent( + database -> { + 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(); + String oldGroupName = oldGroupDef.getName(); + + 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 + 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 found more than 2 groups, so we cannot simply remove old + // assignment + boolean removePreviousAssignments = groupsWithSameName < 2; + + 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 + refresh(); + return; + } + + if (groupTypeEqual + && onlyMinorChanges( + oldGroup.getGroupNode().getGroup(), group)) { + oldGroup.getGroupNode() + .setGroup(group, true, true, database.getEntries()); + + writeGroupChangesToMetaData(); + refresh(); + return; + } + + // Major modifications + + 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 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)"); + } + 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); + + int groupsWithSameName = 0; + 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(); + } + // okay we found more than 2 groups with the same name + // If we only found one we can still do it + if (groupsWithSameName >= 2) { + 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)) { + return; + } + + // stateManager.getEntriesInCurrentDatabase()); + + // TODO: Add undo + // Store undo information. + // AbstractUndoableEdit undoAddPreviousEntries = null; + // UndoableModifyGroup undo = new + // UndoableModifyGroup(GroupSelector.this, groupsRoot, node, + // newGroup); + // if (undoAddPreviousEntries == null) { + // panel.getUndoManager().addEdit(undo); + // } else { + // 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); + // } + + 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 + refresh(); + }); + }); } public void chatWithGroup(GroupNodeViewModel group) { - // This should probably be done some other way. Please don't blame, it's just a thing to make it quick and fast. + // This should probably be done some other way. Please don't blame, it's just a thing to + // make it quick and fast. if (currentDatabase.isEmpty()) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to chat with group"), Localization.lang("No library is selected.")); + dialogService.showErrorDialogAndWait( + Localization.lang("Unable to chat with group"), + Localization.lang("No library is selected.")); return; } @@ -396,32 +545,45 @@ 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()))); - - ObservableList chatHistory = chatHistoryService.getChatHistoryForGroup(group.getGroupNode()); - ObservableList bibEntries = FXCollections.observableArrayList(group.getGroupNode().findMatches(currentDatabase.get().getDatabase())); + StringProperty nameProperty = + new SimpleStringProperty(Localization.lang("Group %0", groupNameProperty.get())); + groupNameProperty.addListener( + (obs, oldValue, newValue) -> + nameProperty.setValue( + Localization.lang("Group %0", groupNameProperty.get()))); + + ObservableList chatHistory = + chatHistoryService.getChatHistoryForGroup(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( - Injector.instantiateModelOrService(AiService.class), - dialogService, - preferences.getAiPreferences(), - preferences.getExternalApplicationsPreferences(), - taskExecutor - ); - - aiChatWindow.setOnCloseRequest(event -> - stateManager.getAiChatWindows().remove(aiChatWindow) - ); + AiChatWindow aiChatWindow = + new AiChatWindow( + Injector.instantiateModelOrService(AiService.class), + dialogService, + preferences.getAiPreferences(), + preferences.getExternalApplicationsPreferences(), + taskExecutor); + + aiChatWindow.setOnCloseRequest( + event -> stateManager.getAiChatWindows().remove(aiChatWindow)); stateManager.getAiChatWindows().add(aiChatWindow); dialogService.showCustomWindow(aiChatWindow); @@ -431,18 +593,23 @@ private void openAiChat(StringProperty name, ObservableList chatHis } public void removeSubgroups(GroupNodeViewModel group) { - boolean confirmation = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove subgroups"), - Localization.lang("Remove all subgroups of \"%0\"?", group.getDisplayName())); + 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); } group.getGroupNode().removeAllChildren(); - dialogService.notify(Localization.lang("Removed all subgroups of group \"%0\".", group.getDisplayName())); + dialogService.notify( + Localization.lang( + "Removed all subgroups of group \"%0\".", group.getDisplayName())); writeGroupChangesToMetaData(); } } @@ -450,35 +617,48 @@ public void removeSubgroups(GroupNodeViewModel group) { public void removeGroupKeepSubgroups(GroupNodeViewModel group) { boolean confirmed; if (selectedGroups.size() <= 1) { - confirmed = dialogService.showConfirmationDialogAndWait( - Localization.lang("Remove group"), - Localization.lang("Remove group \"%0\" and keep its subgroups?", group.getDisplayName()), - Localization.lang("Remove")); + 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"), - Localization.lang("Remove all selected groups and keep their subgroups?"), - Localization.lang("Remove all")); + 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); - selectedGroupNodes.forEach(eachNode -> { - GroupTreeNode groupNode = eachNode.getGroupNode(); - - groupNode.getParent() - .ifPresent(parent -> groupNode.moveAllChildrenTo(parent, parent.getIndexOfChild(groupNode).get())); - groupNode.removeFromParent(); - }); + selectedGroupNodes.forEach( + eachNode -> { + GroupTreeNode groupNode = eachNode.getGroupNode(); + + groupNode + .getParent() + .ifPresent( + parent -> + groupNode.moveAllChildrenTo( + parent, + parent.getIndexOfChild(groupNode).get())); + groupNode.removeFromParent(); + }); if (selectedGroupNodes.size() > 1) { dialogService.notify(Localization.lang("Removed all selected groups.")); } else { - dialogService.notify(Localization.lang("Removed group \"%0\".", group.getDisplayName())); + dialogService.notify( + Localization.lang("Removed group \"%0\".", group.getDisplayName())); } writeGroupChangesToMetaData(); } @@ -490,32 +670,41 @@ 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"), - Localization.lang("Remove group \"%0\" and its subgroups?", group.getDisplayName()), - Localization.lang("Remove")); + 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"), - Localization.lang("Remove all selected groups and their subgroups?"), - Localization.lang("Remove all")); + 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); - selectedGroupNodes.forEach(eachNode -> { - removeGroupsAndSubGroupsFromEntries(eachNode); - eachNode.getGroupNode().removeFromParent(); - }); + selectedGroupNodes.forEach( + eachNode -> { + removeGroupsAndSubGroupsFromEntries(eachNode); + eachNode.getGroupNode().removeFromParent(); + }); if (selectedGroupNodes.size() > 1) { - dialogService.notify(Localization.lang("Removed all selected groups and their subgroups.")); + dialogService.notify( + Localization.lang("Removed all selected groups and their subgroups.")); } else { - dialogService.notify(Localization.lang("Removed group \"%0\" and its subgroups.", group.getDisplayName())); + dialogService.notify( + Localization.lang( + "Removed group \"%0\" and its subgroups.", group.getDisplayName())); } writeGroupChangesToMetaData(); } @@ -527,32 +716,37 @@ 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")); + 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")); + 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); - selectedGroupNodes.forEach(eachNode -> { - removeGroupsAndSubGroupsFromEntries(eachNode); - eachNode.getGroupNode().removeFromParent(); - }); + selectedGroupNodes.forEach( + eachNode -> { + removeGroupsAndSubGroupsFromEntries(eachNode); + eachNode.getGroupNode().removeFromParent(); + }); if (selectedGroupNodes.size() > 1) { dialogService.notify(Localization.lang("Removed all selected groups.")); } else { - dialogService.notify(Localization.lang("Removed group \"%0\".", group.getDisplayName())); + dialogService.notify( + Localization.lang("Removed group \"%0\".", group.getDisplayName())); } writeGroupChangesToMetaData(); } @@ -569,10 +763,16 @@ void removeGroupsAndSubGroupsFromEntries(GroupNodeViewModel group) { String name = group.getGroupNode().getGroup().getName(); Optional rootGroup = currentDatabase.get().getMetaData().getGroups(); if (rootGroup.isPresent()) { - groupsWithSameName = rootGroup.get().findChildrenSatisfying(g -> g.getName().equals(name)).size(); + 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); } } @@ -580,14 +780,17 @@ void removeGroupsAndSubGroupsFromEntries(GroupNodeViewModel group) { public void addSelectedEntries(GroupNodeViewModel group) { // TODO: Warn - // if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(node.getNode().getGroup(), panel.frame())) { + // 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 @@ -601,21 +804,24 @@ public void addSelectedEntries(GroupNodeViewModel group) { // if (assignedEntries == 1) { // 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), + // 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())) { + // 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) { diff --git a/src/main/java/org/jabref/gui/groups/GroupViewMode.java b/src/main/java/org/jabref/gui/groups/GroupViewMode.java index 23784615fa33..783542c3a196 100644 --- a/src/main/java/org/jabref/gui/groups/GroupViewMode.java +++ b/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/src/main/java/org/jabref/gui/groups/GroupsPreferences.java b/src/main/java/org/jabref/gui/groups/GroupsPreferences.java index 9c71262b4de8..9d4c10f235d8 100644 --- a/src/main/java/org/jabref/gui/groups/GroupsPreferences.java +++ b/src/main/java/org/jabref/gui/groups/GroupsPreferences.java @@ -1,6 +1,6 @@ package org.jabref.gui.groups; -import java.util.EnumSet; +import com.google.common.annotations.VisibleForTesting; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; @@ -12,7 +12,7 @@ import org.jabref.model.groups.GroupHierarchyType; -import com.google.common.annotations.VisibleForTesting; +import java.util.EnumSet; public class GroupsPreferences { @@ -21,12 +21,13 @@ public class GroupsPreferences { 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 +46,11 @@ 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); diff --git a/src/main/java/org/jabref/gui/groups/MoveGroupChange.java b/src/main/java/org/jabref/gui/groups/MoveGroupChange.java index e52f9cbef8d7..5c11490630dd 100644 --- a/src/main/java/org/jabref/gui/groups/MoveGroupChange.java +++ b/src/main/java/org/jabref/gui/groups/MoveGroupChange.java @@ -13,7 +13,11 @@ public class MoveGroupChange { * @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) { + public MoveGroupChange( + GroupTreeNode oldParent, + int oldChildIndex, + GroupTreeNode newParent, + int newChildIndex) { this.oldParent = oldParent; this.oldChildIndex = oldChildIndex; this.newParent = newParent; diff --git a/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java b/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java index 08ff81550f40..7ccbb9572f4a 100644 --- a/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java +++ b/src/main/java/org/jabref/gui/groups/UndoableAddOrRemoveGroup.java @@ -1,11 +1,11 @@ package org.jabref.gui.groups; -import java.util.List; - import org.jabref.gui.undo.AbstractUndoableJabRefEdit; import org.jabref.logic.l10n.Localization; import org.jabref.model.groups.GroupTreeNode; +import java.util.List; + public class UndoableAddOrRemoveGroup extends AbstractUndoableJabRefEdit { /** @@ -63,17 +63,17 @@ public class UndoableAddOrRemoveGroup extends AbstractUndoableJabRefEdit { * 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() - : GroupTreeNode.fromGroup(editedNode.getNode().getGroup().deepCopy()); + 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 // robust. @@ -122,8 +122,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); @@ -141,8 +140,7 @@ private void doOperation(boolean undo) { 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); diff --git a/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java b/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java index 3f4b36b63516..e9015dcaee35 100644 --- a/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java +++ b/src/main/java/org/jabref/gui/groups/UndoableChangeEntriesOfGroup.java @@ -1,22 +1,23 @@ package org.jabref.gui.groups; -import java.util.List; - -import javax.swing.undo.AbstractUndoableEdit; - import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.logic.l10n.Localization; import org.jabref.model.FieldChange; +import java.util.List; + +import javax.swing.undo.AbstractUndoableEdit; + public class UndoableChangeEntriesOfGroup { - private UndoableChangeEntriesOfGroup() { - } + private UndoableChangeEntriesOfGroup() {} - public static AbstractUndoableEdit getUndoableEdit(GroupTreeNodeViewModel node, List changes) { + public static AbstractUndoableEdit getUndoableEdit( + GroupTreeNodeViewModel node, List changes) { boolean hasEntryChanges = false; - NamedCompound entryChangeCompound = new NamedCompound(Localization.lang("change entries of group")); + NamedCompound entryChangeCompound = + new NamedCompound(Localization.lang("change entries of group")); for (FieldChange fieldChange : changes) { hasEntryChanges = true; entryChangeCompound.addEdit(new UndoableFieldChange(fieldChange)); diff --git a/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java b/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java index 8f92c7172598..00405f1be0c9 100644 --- a/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java +++ b/src/main/java/org/jabref/gui/groups/UndoableModifySubtree.java @@ -1,11 +1,11 @@ package org.jabref.gui.groups; -import java.util.ArrayList; -import java.util.List; - import org.jabref.gui.undo.AbstractUndoableJabRefEdit; import org.jabref.model.groups.GroupTreeNode; +import java.util.ArrayList; +import java.util.List; + public class UndoableModifySubtree extends AbstractUndoableJabRefEdit { /** @@ -30,8 +30,8 @@ public class UndoableModifySubtree extends AbstractUndoableJabRefEdit { /** * @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,7 +62,8 @@ 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/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java b/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java index c4f367d31f78..1dd3e9997439 100644 --- a/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java +++ b/src/main/java/org/jabref/gui/groups/UndoableMoveGroup.java @@ -1,12 +1,12 @@ package org.jabref.gui.groups; -import java.util.List; -import java.util.Objects; - import org.jabref.gui.undo.AbstractUndoableJabRefEdit; import org.jabref.logic.l10n.Localization; import org.jabref.model.groups.GroupTreeNode; +import java.util.List; +import java.util.Objects; + class UndoableMoveGroup extends AbstractUndoableJabRefEdit { private final GroupTreeNodeViewModel root; diff --git a/src/main/java/org/jabref/gui/help/AboutAction.java b/src/main/java/org/jabref/gui/help/AboutAction.java index 9cc3a5447ee8..5c0a6dea96f9 100644 --- a/src/main/java/org/jabref/gui/help/AboutAction.java +++ b/src/main/java/org/jabref/gui/help/AboutAction.java @@ -1,10 +1,10 @@ package org.jabref.gui.help; +import com.airhacks.afterburner.injection.Injector; + import org.jabref.gui.DialogService; import org.jabref.gui.actions.SimpleCommand; -import com.airhacks.afterburner.injection.Injector; - public class AboutAction extends SimpleCommand { private final AboutDialogView aboutDialogView; diff --git a/src/main/java/org/jabref/gui/help/AboutDialogView.java b/src/main/java/org/jabref/gui/help/AboutDialogView.java index 82e735656c2a..a8335d54331b 100644 --- a/src/main/java/org/jabref/gui/help/AboutDialogView.java +++ b/src/main/java/org/jabref/gui/help/AboutDialogView.java @@ -1,5 +1,9 @@ package org.jabref.gui.help; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.fxml.FXML; import javafx.scene.control.ButtonType; import javafx.scene.control.TextArea; @@ -12,9 +16,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BuildInfo; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - public class AboutDialogView extends BaseDialog { @FXML private ButtonType copyVersionButton; @@ -30,11 +31,10 @@ public class AboutDialogView extends BaseDialog { public AboutDialogView() { this.setTitle(Localization.lang("About JabRef")); - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); - ControlHelper.setAction(copyVersionButton, getDialogPane(), event -> copyVersionToClipboard()); + ControlHelper.setAction( + copyVersionButton, getDialogPane(), event -> copyVersionToClipboard()); } public AboutDialogViewModel getViewModel() { @@ -43,7 +43,8 @@ public AboutDialogViewModel getViewModel() { @FXML private void initialize() { - viewModel = new AboutDialogViewModel(dialogService, preferences, clipBoardManager, buildInfo); + viewModel = + new AboutDialogViewModel(dialogService, preferences, clipBoardManager, buildInfo); textAreaVersions.setText(viewModel.getVersionInfo()); this.setResizable(false); diff --git a/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java b/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java index 44beaf93ab62..3885eb57c4d2 100644 --- a/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java +++ b/src/main/java/org/jabref/gui/help/AboutDialogViewModel.java @@ -1,9 +1,6 @@ package org.jabref.gui.help; -import java.io.IOException; -import java.util.Locale; -import java.util.Objects; -import java.util.stream.Collectors; +import com.google.common.collect.Lists; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; @@ -17,20 +14,26 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BuildInfo; - -import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; + public class AboutDialogViewModel extends AbstractViewModel { private static final String HOMEPAGE_URL = "https://www.jabref.org"; private static final String DONATION_URL = "https://donations.jabref.org"; - private static final String LIBRARIES_URL = "https://github.com/JabRef/jabref/blob/main/external-libraries.md"; + private static final String LIBRARIES_URL = + "https://github.com/JabRef/jabref/blob/main/external-libraries.md"; private static final String GITHUB_URL = "https://github.com/JabRef/jabref"; private static final String LICENSE_URL = "https://github.com/JabRef/jabref/blob/main/LICENSE"; - private static final String CONTRIBUTORS_URL = "https://github.com/JabRef/jabref/graphs/contributors"; - private static final String PRIVACY_POLICY_URL = "https://github.com/JabRef/jabref/blob/main/PRIVACY.md"; + private static final String CONTRIBUTORS_URL = + "https://github.com/JabRef/jabref/graphs/contributors"; + private static final String PRIVACY_POLICY_URL = + "https://github.com/JabRef/jabref/blob/main/PRIVACY.md"; private final String changelogUrl; private final String versionInfo; private final ReadOnlyStringWrapper environmentInfo = new ReadOnlyStringWrapper(); @@ -44,7 +47,11 @@ public class AboutDialogViewModel extends AbstractViewModel { 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); @@ -55,18 +62,29 @@ public AboutDialogViewModel(DialogService dialogService, GuiPreferences preferen isDevelopmentVersion.set(false); } 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); - - 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); + 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); } public String getDevelopmentVersion() { diff --git a/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java b/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java index a27ffacee40e..a4fd2ad96e54 100644 --- a/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java +++ b/src/main/java/org/jabref/gui/help/ErrorConsoleAction.java @@ -1,11 +1,11 @@ package org.jabref.gui.help; +import com.airhacks.afterburner.injection.Injector; + import org.jabref.gui.DialogService; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.errorconsole.ErrorConsoleView; -import com.airhacks.afterburner.injection.Injector; - /** * Such an error console can be * useful in getting complete bug reports, especially from Windows users, diff --git a/src/main/java/org/jabref/gui/help/HelpAction.java b/src/main/java/org/jabref/gui/help/HelpAction.java index fba12d479fc6..0c59206ce895 100644 --- a/src/main/java/org/jabref/gui/help/HelpAction.java +++ b/src/main/java/org/jabref/gui/help/HelpAction.java @@ -16,7 +16,10 @@ public class HelpAction extends SimpleCommand { 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; @@ -25,8 +28,8 @@ public HelpAction(HelpFile helpPage, DialogService dialogService, ExternalApplic void openHelpPage(HelpFile helpPage) { StringBuilder sb = new StringBuilder("https://docs.jabref.org/"); sb.append(helpPage.getPageName()); - NativeDesktop.openBrowserShowPopup(sb.toString(), dialogService, externalApplicationPreferences - ); + NativeDesktop.openBrowserShowPopup( + sb.toString(), dialogService, externalApplicationPreferences); } @Override diff --git a/src/main/java/org/jabref/gui/help/NewVersionDialog.java b/src/main/java/org/jabref/gui/help/NewVersionDialog.java index 02242e49ebbb..f755dc5cb581 100644 --- a/src/main/java/org/jabref/gui/help/NewVersionDialog.java +++ b/src/main/java/org/jabref/gui/help/NewVersionDialog.java @@ -16,38 +16,60 @@ 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 btnDownloadUpdate = new ButtonType(Localization.lang("Download update"), ButtonBar.ButtonData.APPLY); - 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); - } - return true; - }); + 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); + 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); + } + return true; + }); Button defaultButton = (Button) this.getDialogPane().lookupButton(btnDownloadUpdate); defaultButton.setDefaultButton(true); - Hyperlink lblMoreInformation = new Hyperlink(Localization.lang("To see what is new view the changelog.")); - lblMoreInformation.setOnAction(event -> - NativeDesktop.openBrowserShowPopup(latestVersion.getChangelogUrl(), dialogService, externalApplicationsPreferences) - ); + Hyperlink lblMoreInformation = + new Hyperlink(Localization.lang("To see what is new view the changelog.")); + lblMoreInformation.setOnAction( + event -> + NativeDesktop.openBrowserShowPopup( + latestVersion.getChangelogUrl(), + dialogService, + externalApplicationsPreferences)); - VBox container = new VBox( - new Label(Localization.lang("A new version of JabRef has been released.")), - new Label(Localization.lang("Installed version") + ": " + currentVersion.getFullVersion()), - new Label(Localization.lang("Latest version") + ": " + latestVersion.getFullVersion()), - lblMoreInformation - ); + VBox container = + new VBox( + new Label(Localization.lang("A new version of JabRef has been released.")), + new Label( + Localization.lang("Installed version") + + ": " + + currentVersion.getFullVersion()), + new Label( + Localization.lang("Latest version") + + ": " + + latestVersion.getFullVersion()), + lblMoreInformation); getDialogPane().setContent(container); getDialogPane().setPrefWidth(450); } diff --git a/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java b/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java index 85edd1234859..077a1fdf2adc 100644 --- a/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java +++ b/src/main/java/org/jabref/gui/help/SearchForUpdateAction.java @@ -1,22 +1,21 @@ package org.jabref.gui.help; +import com.airhacks.afterburner.injection.Injector; + import org.jabref.gui.DialogService; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.logic.util.BuildInfo; import org.jabref.logic.util.TaskExecutor; -import com.airhacks.afterburner.injection.Injector; - 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; diff --git a/src/main/java/org/jabref/gui/help/VersionWorker.java b/src/main/java/org/jabref/gui/help/VersionWorker.java index a68781ef4a43..eaecf61e9769 100644 --- a/src/main/java/org/jabref/gui/help/VersionWorker.java +++ b/src/main/java/org/jabref/gui/help/VersionWorker.java @@ -1,11 +1,5 @@ package org.jabref.gui.help; -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - import org.jabref.gui.DialogService; import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.gui.preferences.GuiPreferences; @@ -14,10 +8,15 @@ import org.jabref.logic.util.BackgroundTask; import org.jabref.logic.util.TaskExecutor; import org.jabref.logic.util.Version; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + /** * 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 @@ -40,10 +39,11 @@ public class VersionWorker { 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); @@ -62,16 +62,16 @@ 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); } /** @@ -80,8 +80,11 @@ public void checkForNewVersionDelayed() { 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); + String tryLater = + Localization.lang( + "Please try again later and/or check your network connection."); + dialogService.showErrorDialogAndWait( + Localization.lang("Error"), couldNotConnect + "\n" + tryLater, exception); } LOGGER.debug("Could not connect to the update server.", exception); } @@ -91,16 +94,24 @@ private void showConnectionError(Exception exception, boolean manualExecution) { * 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 { // 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/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index 0cd333668963..749090120b92 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -1,20 +1,6 @@ package org.jabref.gui.icon; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.ServiceLoader; -import java.util.Set; +import static java.util.EnumSet.allOf; import javafx.scene.Node; import javafx.scene.control.Button; @@ -23,7 +9,6 @@ import javafx.scene.paint.Color; import org.jabref.architecture.AllowedToUseClassGetResource; - import org.kordamp.ikonli.Ikon; import org.kordamp.ikonli.IkonProvider; import org.kordamp.ikonli.materialdesign2.MaterialDesignA; @@ -50,7 +35,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.util.EnumSet.allOf; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.Set; @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") public class IconTheme { @@ -59,7 +58,9 @@ public class IconTheme { 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 +71,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() { @@ -107,10 +110,14 @@ private static Image getImageFX(String name) { 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); + return Objects.requireNonNull( + IconTheme.class.getResource(path), "Path must not be null for key " + key); } /** @@ -130,8 +137,9 @@ private static Map readIconThemeFile(URL url, String prefix) { Map result = new HashMap<>(); - try (BufferedReader in = new BufferedReader( - new InputStreamReader(url.openStream(), StandardCharsets.ISO_8859_1))) { + try (BufferedReader in = + new BufferedReader( + new InputStreamReader(url.openStream(), StandardCharsets.ISO_8859_1))) { String line; while ((line = in.readLine()) != null) { if (!line.contains("=")) { @@ -163,7 +171,6 @@ public static List getLogoSetFX() { } public enum JabRefIcons implements JabRefIcon { - ADD(MaterialDesignP.PLUS_CIRCLE_OUTLINE), ADD_FILLED(MaterialDesignP.PLUS_CIRCLE), ADD_NOBOX(MaterialDesignP.PLUS), @@ -215,11 +222,36 @@ public enum JabRefIcons implements JabRefIcon { 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), + 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), diff --git a/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java b/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java index 3205426df650..cf182b029a31 100644 --- a/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java +++ b/src/main/java/org/jabref/gui/icon/InternalMaterialDesignIcon.java @@ -1,18 +1,17 @@ package org.jabref.gui.icon; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import javafx.scene.Node; import javafx.scene.paint.Color; import org.jabref.gui.util.ColorUtil; - import org.kordamp.ikonli.Ikon; import org.kordamp.ikonli.javafx.FontIcon; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + public class InternalMaterialDesignIcon implements JabRefIcon { private final List icons; @@ -34,7 +33,11 @@ public InternalMaterialDesignIcon(Ikon... icons) { public InternalMaterialDesignIcon(List icons) { this.icons = icons; - this.unicode = icons.stream().map(Ikon::getCode).map(String::valueOf).collect(Collectors.joining()); + this.unicode = + icons.stream() + .map(Ikon::getCode) + .map(String::valueOf) + .collect(Collectors.joining()); this.color = Optional.empty(); } @@ -45,9 +48,13 @@ 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; } diff --git a/src/main/java/org/jabref/gui/icon/JabRefIconView.java b/src/main/java/org/jabref/gui/icon/JabRefIconView.java index ed0f4e54b293..207201239513 100644 --- a/src/main/java/org/jabref/gui/icon/JabRefIconView.java +++ b/src/main/java/org/jabref/gui/icon/JabRefIconView.java @@ -1,13 +1,13 @@ package org.jabref.gui.icon; +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.css.Size; import javafx.css.SizeUnits; import org.jabref.gui.icon.IconTheme.JabRefIcons; - -import com.tobiasdiez.easybind.EasyBind; import org.kordamp.ikonli.javafx.FontIcon; public class JabRefIconView extends FontIcon { @@ -17,6 +17,7 @@ public class JabRefIconView extends FontIcon { * (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) { diff --git a/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java b/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java index b09703d91898..381672963224 100644 --- a/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java +++ b/src/main/java/org/jabref/gui/icon/JabRefIkonHandler.java @@ -1,13 +1,12 @@ package org.jabref.gui.icon; -import java.io.InputStream; -import java.net.URL; - import org.jabref.architecture.AllowedToUseClassGetResource; - import org.kordamp.ikonli.AbstractIkonHandler; import org.kordamp.ikonli.Ikon; +import java.io.InputStream; +import java.net.URL; + @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") public class JabRefIkonHandler extends AbstractIkonHandler { diff --git a/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java b/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java index 3dd28d6d2427..c2bd5502ffb1 100644 --- a/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java +++ b/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java @@ -14,7 +14,6 @@ * @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'), diff --git a/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java b/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java index 27735d96c4a5..7cc3dc530ec4 100644 --- a/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java +++ b/src/main/java/org/jabref/gui/importer/BibEntryTypePrefsAndFileViewModel.java @@ -4,15 +4,16 @@ 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 */ @Override public String toString() { - return Localization.lang("%0 (from file)\n%1 (current setting)", + return Localization.lang( + "%0 (from file)\n%1 (current setting)", MetaDataSerializer.serializeCustomEntryTypes(customTypeFromFile), MetaDataSerializer.serializeCustomEntryTypes(customTypeFromPreferences)); } } - diff --git a/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdAction.java b/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdAction.java index 581be1802dab..051ce63f70a0 100644 --- a/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdAction.java +++ b/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdAction.java @@ -1,7 +1,6 @@ package org.jabref.gui.importer; -import java.util.Optional; - +import org.controlsfx.control.PopOver; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -18,11 +17,11 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.util.FileUpdateMonitor; - -import org.controlsfx.control.PopOver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Optional; + public class GenerateEntryFromIdAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(GenerateEntryFromIdAction.class); @@ -35,14 +34,15 @@ public class GenerateEntryFromIdAction extends SimpleCommand { private final StateManager stateManager; private final FileUpdateMonitor fileUpdateMonitor; - public GenerateEntryFromIdAction(LibraryTab libraryTab, - DialogService dialogService, - GuiPreferences preferences, - TaskExecutor taskExecutor, - PopOver entryFromIdPopOver, - String identifier, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor) { + public GenerateEntryFromIdAction( + LibraryTab libraryTab, + DialogService dialogService, + GuiPreferences preferences, + TaskExecutor taskExecutor, + PopOver entryFromIdPopOver, + String identifier, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor) { this.libraryTab = libraryTab; this.dialogService = dialogService; this.preferences = preferences; @@ -58,46 +58,67 @@ public void execute() { BackgroundTask> backgroundTask = searchAndImportEntryInBackground(); backgroundTask.titleProperty().set(Localization.lang("Import by ID")); backgroundTask.showToUser(true); - backgroundTask.onRunning(() -> { - entryFromIdPopOver.hide(); - dialogService.notify("%s".formatted(backgroundTask.messageProperty().get())); - }); - backgroundTask.onFailure(exception -> { - String fetcherExceptionMessage = exception.getMessage(); + backgroundTask.onRunning( + () -> { + entryFromIdPopOver.hide(); + dialogService.notify("%s".formatted(backgroundTask.messageProperty().get())); + }); + backgroundTask.onFailure( + exception -> { + String fetcherExceptionMessage = exception.getMessage(); - String msg; - if (exception instanceof FetcherClientException) { - msg = Localization.lang("Bibliographic data not found. Cause is likely the client side. Please check connection and identifier for correctness.") + "\n" + fetcherExceptionMessage; - } else if (exception instanceof FetcherServerException) { - msg = Localization.lang("Bibliographic data not found. Cause is likely the server side. Please try again later.") + "\n" + fetcherExceptionMessage; - } else { - msg = Localization.lang("Error message %0", fetcherExceptionMessage); - } + String msg; + if (exception instanceof FetcherClientException) { + msg = + Localization.lang( + "Bibliographic data not found. Cause is likely the client side. Please check connection and identifier for correctness.") + + "\n" + + fetcherExceptionMessage; + } else if (exception instanceof FetcherServerException) { + msg = + Localization.lang( + "Bibliographic data not found. Cause is likely the server side. Please try again later.") + + "\n" + + fetcherExceptionMessage; + } else { + msg = Localization.lang("Error message %0", fetcherExceptionMessage); + } - LOGGER.info(fetcherExceptionMessage, exception); + LOGGER.info(fetcherExceptionMessage, exception); - if (dialogService.showConfirmationDialogAndWait(Localization.lang("Failed to import by ID"), msg, Localization.lang("Add entry manually"))) { - // add entry manually - new NewEntryAction(() -> libraryTab, StandardEntryType.Article, dialogService, - preferences, stateManager).execute(); - } - }); - backgroundTask.onSuccess(result -> { - if (result.isPresent()) { - final BibEntry entry = result.get(); - ImportHandler handler = new ImportHandler( - libraryTab.getBibDatabaseContext(), - preferences, - fileUpdateMonitor, - libraryTab.getUndoManager(), - stateManager, - dialogService, - taskExecutor); - handler.importEntryWithDuplicateCheck(libraryTab.getBibDatabaseContext(), entry); - } else { - dialogService.notify("No entry found or import canceled"); - } - }); + if (dialogService.showConfirmationDialogAndWait( + Localization.lang("Failed to import by ID"), + msg, + Localization.lang("Add entry manually"))) { + // add entry manually + new NewEntryAction( + () -> libraryTab, + StandardEntryType.Article, + dialogService, + preferences, + stateManager) + .execute(); + } + }); + backgroundTask.onSuccess( + result -> { + if (result.isPresent()) { + final BibEntry entry = result.get(); + ImportHandler handler = + new ImportHandler( + libraryTab.getBibDatabaseContext(), + preferences, + fileUpdateMonitor, + libraryTab.getUndoManager(), + stateManager, + dialogService, + taskExecutor); + handler.importEntryWithDuplicateCheck( + libraryTab.getBibDatabaseContext(), entry); + } else { + dialogService.notify("No entry found or import canceled"); + } + }); backgroundTask.executeWith(taskExecutor); } @@ -109,7 +130,8 @@ public Optional call() throws FetcherException { return Optional.empty(); } updateMessage(Localization.lang("Searching...")); - return new CompositeIdFetcher(preferences.getImportFormatPreferences()).performSearchById(identifier); + return new CompositeIdFetcher(preferences.getImportFormatPreferences()) + .performSearchById(identifier); } }; } diff --git a/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdDialog.java b/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdDialog.java index 953563730459..3c06779e7746 100644 --- a/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdDialog.java +++ b/src/main/java/org/jabref/gui/importer/GenerateEntryFromIdDialog.java @@ -1,10 +1,15 @@ package org.jabref.gui.importer; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.DialogPane; import javafx.scene.control.TextField; +import org.controlsfx.control.PopOver; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -14,10 +19,6 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; -import org.controlsfx.control.PopOver; - public class GenerateEntryFromIdDialog { @FXML DialogPane dialogPane; @@ -34,7 +35,12 @@ public class GenerateEntryFromIdDialog { private PopOver entryFromIdPopOver; - public GenerateEntryFromIdDialog(LibraryTab libraryTab, DialogService dialogService, GuiPreferences preferences, TaskExecutor taskExecutor, StateManager stateManager) { + public GenerateEntryFromIdDialog( + LibraryTab libraryTab, + DialogService dialogService, + GuiPreferences preferences, + TaskExecutor taskExecutor, + StateManager stateManager) { ViewLoader.view(this).load(); this.preferences = preferences; this.dialogService = dialogService; @@ -45,7 +51,8 @@ public GenerateEntryFromIdDialog(LibraryTab libraryTab, DialogService dialogServ this.generateButton.setDefaultButton(true); } - @FXML private void generateEntry() { + @FXML + private void generateEntry() { if (idTextField.getText().isEmpty()) { dialogService.notify(Localization.lang("Enter a valid ID")); return; @@ -53,16 +60,16 @@ public GenerateEntryFromIdDialog(LibraryTab libraryTab, DialogService dialogServ this.idTextField.requestFocus(); - GenerateEntryFromIdAction generateEntryFromIdAction = new GenerateEntryFromIdAction( - libraryTab, - dialogService, - preferences, - taskExecutor, - entryFromIdPopOver, - idTextField.getText(), - stateManager, - fileUpdateMonitor - ); + GenerateEntryFromIdAction generateEntryFromIdAction = + new GenerateEntryFromIdAction( + libraryTab, + dialogService, + preferences, + taskExecutor, + entryFromIdPopOver, + idTextField.getText(), + stateManager, + fileUpdateMonitor); generateEntryFromIdAction.execute(); } diff --git a/src/main/java/org/jabref/gui/importer/GrobidOptInDialogHelper.java b/src/main/java/org/jabref/gui/importer/GrobidOptInDialogHelper.java index b049e104c320..fe32ddc6de78 100644 --- a/src/main/java/org/jabref/gui/importer/GrobidOptInDialogHelper.java +++ b/src/main/java/org/jabref/gui/importer/GrobidOptInDialogHelper.java @@ -18,18 +18,21 @@ public class GrobidOptInDialogHelper { * @param dialogService the DialogService to use * @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) { + public static boolean showAndWaitIfUserIsUndecided( + DialogService dialogService, GrobidPreferences preferences) { if (preferences.isGrobidEnabled()) { return true; } if (preferences.isGrobidOptOut()) { return false; } - boolean grobidEnabled = dialogService.showConfirmationDialogWithOptOutAndWait( - 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("Do not ask again"), - optOut -> preferences.setGrobidOptOut(optOut)); + boolean grobidEnabled = + dialogService.showConfirmationDialogWithOptOutAndWait( + 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("Do not ask again"), + optOut -> preferences.setGrobidOptOut(optOut)); preferences.setGrobidEnabled(grobidEnabled); return grobidEnabled; } diff --git a/src/main/java/org/jabref/gui/importer/ImportCommand.java b/src/main/java/org/jabref/gui/importer/ImportCommand.java index 55469924eb92..0a85bd2b1b0a 100644 --- a/src/main/java/org/jabref/gui/importer/ImportCommand.java +++ b/src/main/java/org/jabref/gui/importer/ImportCommand.java @@ -1,13 +1,6 @@ package org.jabref.gui.importer; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.SortedSet; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import javafx.stage.FileChooser; @@ -33,11 +26,17 @@ import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabase; import org.jabref.model.util.FileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.SortedSet; /** * Perform an import action @@ -45,7 +44,10 @@ 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; @@ -55,13 +57,14 @@ public enum ImportMethod { AS_NEW, TO_EXISTING } 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,48 +79,72 @@ 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(); - dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(path -> importSingleFile(path, importers, fileDialogConfiguration.getSelectedExtensionFilter())); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .addExtensionFilter(FileFilterConverter.ANY_FILE) + .addExtensionFilter(FileFilterConverter.forAllImporters(importers)) + .addExtensionFilter( + FileFilterConverter.importerToExtensionFilter(importers)) + .withInitialDirectory( + preferences.getImporterPreferences().getImportWorkingDirectory()) + .build(); + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + path -> + importSingleFile( + path, + importers, + fileDialogConfiguration.getSelectedExtensionFilter())); } - private void importSingleFile(Path file, SortedSet importers, FileChooser.ExtensionFilter selectedExtensionFilter) { + private void importSingleFile( + Path file, + SortedSet importers, + FileChooser.ExtensionFilter selectedExtensionFilter) { if (!Files.exists(file)) { - dialogService.showErrorDialogAndWait(Localization.lang("Import"), + dialogService.showErrorDialogAndWait( + Localization.lang("Import"), Localization.lang("File not found") + ": '" + file.getFileName() + "'."); return; } - Optional format = FileFilterConverter.getImporter(selectedExtensionFilter, importers); - BackgroundTask task = BackgroundTask.wrap( - () -> doImport(Collections.singletonList(file), format.orElse(null))); + Optional format = + FileFilterConverter.getImporter(selectedExtensionFilter, importers); + BackgroundTask task = + BackgroundTask.wrap( + () -> doImport(Collections.singletonList(file), format.orElse(null))); if (importMethod == ImportMethod.AS_NEW) { - task.onSuccess(parserResult -> { - tabContainer.addTab(parserResult.getDatabaseContext(), true); - dialogService.notify(Localization.lang("Imported entries") + ": " + 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); + task.onSuccess( + parserResult -> { + tabContainer.addTab(parserResult.getDatabaseContext(), true); + dialogService.notify( + Localization.lang("Imported entries") + + ": " + + 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(tabContainer.getCurrentLibraryTab().getBibDatabaseContext(), task); + ImportEntriesDialog dialog = + new ImportEntriesDialog( + tabContainer.getCurrentLibraryTab().getBibDatabaseContext(), task); dialog.setTitle(Localization.lang("Import")); dialogService.showCustomDialogAndWait(dialog); } @@ -133,52 +160,74 @@ 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) && GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { - importFormatReader.reset(); - } - dialogService.notify(Localization.lang("Importing file %0 as unknown format", filename.getFileName().toString())); - }); + UiTaskExecutor.runAndWaitInJavaFXThread( + () -> { + if (FileUtil.isPDFFile(filename) + && GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided( + dialogService, + preferences.getGrobidPreferences())) { + importFormatReader.reset(); + } + 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)); + imports.add( + importFormatReader.importUnknownFormat(filename, fileUpdateMonitor)); } else { - UiTaskExecutor.runAndWaitInJavaFXThread(() -> { - if (((importer.get() instanceof PdfGrobidImporter) - || (importer.get() instanceof PdfMergeMetadataImporter)) - && GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided(dialogService, preferences.getGrobidPreferences())) { - importFormatReader.reset(); - } - dialogService.notify(Localization.lang("Importing in %0 format", importer.get().getName()) + "..."); - }); + UiTaskExecutor.runAndWaitInJavaFXThread( + () -> { + if (((importer.get() instanceof PdfGrobidImporter) + || (importer.get() + instanceof PdfMergeMetadataImporter)) + && GrobidOptInDialogHelper.showAndWaitIfUserIsUndecided( + dialogService, + preferences.getGrobidPreferences())) { + importFormatReader.reset(); + } + 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)); + imports.add( + new ImportFormatReader.UnknownFormatImport( + importer.get().getName(), pr)); } } 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."))); + () -> + dialogService.showWarningDialogAndWait( + Localization.lang("Import error"), + Localization.lang( + "No entries found. Please make sure you are using the correct import filter."))); return new ParserResult(); } @@ -202,17 +251,26 @@ public ParserResult mergeImportResults(List path.getFileName().toString()).orElse("unknown"), - parserResult.getDatabase().getEntries()); + new DatabaseMerger(preferences.getBibEntryPreferences().getKeywordSeparator()) + .mergeMetaData( + 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/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java b/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java index 849478abb0b6..7266dcf5fc7a 100644 --- a/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java +++ b/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialog.java @@ -1,6 +1,8 @@ package org.jabref.gui.importer; -import java.util.List; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.beans.binding.Bindings; import javafx.fxml.FXML; @@ -8,15 +10,14 @@ import javafx.scene.control.cell.CheckBoxListCell; import javafx.scene.layout.VBox; +import org.controlsfx.control.CheckListView; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntryType; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; -import org.controlsfx.control.CheckListView; +import java.util.List; public class ImportCustomEntryTypesDialog extends BaseDialog { @@ -35,20 +36,24 @@ public ImportCustomEntryTypesDialog(BibDatabaseMode mode, List cus 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()); - } - return null; - }); + setResultConverter( + btn -> { + if (btn == ButtonType.OK) { + viewModel.importBibEntryTypes( + unknownEntryTypesCheckList.getCheckModel().getCheckedItems(), + differentCustomizationCheckList + .getCheckModel() + .getCheckedItems() + .stream() + .map( + BibEntryTypePrefsAndFileViewModel + ::customTypeFromPreferences) + .toList()); + } + return null; + }); setTitle(Localization.lang("Custom entry types")); } @@ -56,16 +61,25 @@ public ImportCustomEntryTypesDialog(BibDatabaseMode mode, List cus @FXML public void initialize() { viewModel = new ImportCustomEntryTypesDialogViewModel(mode, customEntryTypes, preferences); - boxDifferentCustomization.visibleProperty().bind(Bindings.isNotEmpty(viewModel.differentCustomizations())); - boxDifferentCustomization.managedProperty().bind(Bindings.isNotEmpty(viewModel.differentCustomizations())); + 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/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java b/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java index b7aa3063effd..b9d5645a2c29 100644 --- a/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/importer/ImportCustomEntryTypesDialogViewModel.java @@ -1,7 +1,6 @@ package org.jabref.gui.importer; -import java.util.List; -import java.util.Optional; +import com.airhacks.afterburner.injection.Injector; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -11,35 +10,44 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.types.EntryTypeFactory; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.Optional; + public class ImportCustomEntryTypesDialogViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(ImportCustomEntryTypesDialogViewModel.class); + 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(); + private final ObservableList differentCustomizationTypes = + FXCollections.observableArrayList(); - public ImportCustomEntryTypesDialogViewModel(BibDatabaseMode mode, List entryTypes, CliPreferences preferences) { + public ImportCustomEntryTypesDialogViewModel( + BibDatabaseMode mode, List entryTypes, CliPreferences preferences) { this.mode = mode; this.preferences = preferences; - BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); + BibEntryTypesManager entryTypesManager = + Injector.instantiateModelOrService(BibEntryTypesManager.class); for (BibEntryType customType : entryTypes) { - Optional currentlyStoredType = entryTypesManager.enrich(customType.getType(), mode); + Optional currentlyStoredType = + entryTypesManager.enrich(customType.getType(), mode); if (currentlyStoredType.isEmpty()) { newTypes.add(customType); } else { - if (!EntryTypeFactory.nameAndFieldsAreEqual(customType, currentlyStoredType.get())) { + 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,14 +61,19 @@ public ObservableList differentCustomizations return this.differentCustomizationTypes; } - public void importBibEntryTypes(List checkedUnknownEntryTypes, List checkedDifferentEntryTypes) { - BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); + public void importBibEntryTypes( + List checkedUnknownEntryTypes, + List checkedDifferentEntryTypes) { + BibEntryTypesManager entryTypesManager = + Injector.instantiateModelOrService(BibEntryTypesManager.class); if (!checkedUnknownEntryTypes.isEmpty()) { - checkedUnknownEntryTypes.forEach(type -> entryTypesManager.addCustomOrModifiedType(type, mode)); + checkedUnknownEntryTypes.forEach( + type -> entryTypesManager.addCustomOrModifiedType(type, mode)); preferences.storeCustomEntryTypesRepository(entryTypesManager); } if (!checkedDifferentEntryTypes.isEmpty()) { - checkedUnknownEntryTypes.forEach(type -> entryTypesManager.addCustomOrModifiedType(type, mode)); + checkedUnknownEntryTypes.forEach( + type -> entryTypesManager.addCustomOrModifiedType(type, mode)); preferences.storeCustomEntryTypesRepository(entryTypesManager); } } diff --git a/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java b/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java index 46298d897a17..5468625322ed 100644 --- a/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java +++ b/src/main/java/org/jabref/gui/importer/ImportEntriesDialog.java @@ -1,8 +1,9 @@ package org.jabref.gui.importer; -import java.util.Optional; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; -import javax.swing.undo.UndoManager; +import jakarta.inject.Inject; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -27,6 +28,8 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; +import org.controlsfx.control.CheckListView; +import org.fxmisc.richtext.CodeArea; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.entryeditor.citationrelationtab.BibEntryView; @@ -46,11 +49,9 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; -import org.controlsfx.control.CheckListView; -import org.fxmisc.richtext.CodeArea; +import java.util.Optional; + +import javax.swing.undo.UndoManager; public class ImportEntriesDialog extends BaseDialog { @@ -86,30 +87,43 @@ public class ImportEntriesDialog extends BaseDialog { 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()); + BooleanBinding booleanBind = + Bindings.isEmpty(entriesListView.getCheckModel().getCheckedItems()); Button btn = (Button) this.getDialogPane().lookupButton(importButton); btn.disableProperty().bind(booleanBind); - downloadLinkedOnlineFiles.setSelected(preferences.getFilePreferences().shouldDownloadLinkedFiles()); + downloadLinkedOnlineFiles.setSelected( + preferences.getFilePreferences().shouldDownloadLinkedFiles()); - setResultConverter(button -> { - if (button == importButton) { - viewModel.importEntries(entriesListView.getCheckModel().getCheckedItems(), downloadLinkedOnlineFiles.isSelected()); - } else { - dialogService.notify(Localization.lang("Import canceled")); - } + setResultConverter( + button -> { + if (button == importButton) { + viewModel.importEntries( + entriesListView.getCheckModel().getCheckedItems(), + downloadLinkedOnlineFiles.isSelected()); + } else { + dialogService.notify(Localization.lang("Import canceled")); + } - return false; - }); + return false; + }); } @FXML private void initialize() { - viewModel = new ImportEntriesViewModel(task, taskExecutor, database, dialogService, undoManager, preferences, stateManager, entryTypesManager, fileUpdateMonitor); + viewModel = + new ImportEntriesViewModel( + task, + taskExecutor, + database, + dialogService, + undoManager, + preferences, + stateManager, + entryTypesManager, + fileUpdateMonitor); Label placeholder = new Label(); placeholder.textProperty().bind(viewModel.messageProperty()); entriesListView.setPlaceholder(placeholder); @@ -118,69 +132,103 @@ private void initialize() { libraryListView.setEditable(false); libraryListView.getItems().addAll(stateManager.getOpenDatabases()); new ViewModelListCellFactory() - .withText(database -> { - Optional dbOpt = Optional.empty(); - if (database.getDatabasePath().isPresent()) { - dbOpt = FileUtil.getUniquePathFragment(stateManager.collectAllDatabasePaths(), database.getDatabasePath().get()); - } - if (database.getLocation() == DatabaseLocation.SHARED) { - return database.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]"; - } + .withText( + database -> { + Optional dbOpt = Optional.empty(); + if (database.getDatabasePath().isPresent()) { + dbOpt = + FileUtil.getUniquePathFragment( + stateManager.collectAllDatabasePaths(), + database.getDatabasePath().get()); + } + if (database.getLocation() == DatabaseLocation.SHARED) { + return database.getDBMSSynchronizer().getDBName() + + " [" + + Localization.lang("shared") + + "]"; + } - return dbOpt.orElseGet(() -> Localization.lang("untitled")); - }) + return dbOpt.orElseGet(() -> Localization.lang("untitled")); + }) .install(libraryListView); - viewModel.selectedDbProperty().bind(libraryListView.getSelectionModel().selectedItemProperty()); - stateManager.getActiveDatabase().ifPresent(database1 -> libraryListView.getSelectionModel().select(database1)); + 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(); - } + .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)); - }) + 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()); + selectedItems + .textProperty() + .bind(Bindings.size(entriesListView.getCheckModel().getCheckedItems()).asString()); totalItems.textProperty().bind(Bindings.size(entriesListView.getItems()).asString()); entriesListView.setSelectionModel(new NoSelectionModel<>()); initBibTeX(); @@ -199,12 +247,21 @@ private void displayBibTeX(BibEntry entry, String bibTeX) { 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); - bibTeXDataBox.setManaged(new_val); - }); + showEntryInformation + .selectedProperty() + .addListener( + (observableValue, old_val, new_val) -> { + bibTeXDataBox.setVisible(new_val); + bibTeXDataBox.setManaged(new_val); + }); } public void unselectAll() { diff --git a/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java b/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java index 383483154561..5ba08bacbf3b 100644 --- a/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java +++ b/src/main/java/org/jabref/gui/importer/ImportEntriesViewModel.java @@ -1,12 +1,5 @@ package org.jabref.gui.importer; -import java.io.IOException; -import java.io.StringWriter; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -33,10 +26,16 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + public class ImportEntriesViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ImportEntriesViewModel.class); @@ -58,15 +57,16 @@ public class ImportEntriesViewModel extends AbstractViewModel { * @param databaseContext the database to import into * @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; @@ -80,18 +80,25 @@ public ImportEntriesViewModel(BackgroundTask task, this.message.bind(task.messageProperty()); this.selectedDb = new SimpleObjectProperty<>(); - task.onSuccess(parserResult -> { - // store the complete parser result (to import groups, ... later on) - this.parserResult = parserResult; - // fill in the list for the user, where one can select the entries to import - entries.addAll(parserResult.getDatabase().getEntries()); - if (entries.isEmpty()) { - task.updateMessage(Localization.lang("No entries corresponding to given query")); - } - }).onFailure(ex -> { - LOGGER.error("Error importing", ex); - dialogService.showErrorDialogAndWait(ex); - }).executeWith(taskExecutor); + task.onSuccess( + parserResult -> { + // store the complete parser result (to import groups, ... later on) + this.parserResult = parserResult; + // fill in the list for the user, where one can select the entries to + // import + entries.addAll(parserResult.getDatabase().getEntries()); + if (entries.isEmpty()) { + task.updateMessage( + Localization.lang( + "No entries corresponding to given query")); + } + }) + .onFailure( + ex -> { + LOGGER.error("Error importing", ex); + dialogService.showErrorDialogAndWait(ex); + }) + .executeWith(taskExecutor); } public String getMessage() { @@ -115,9 +122,13 @@ 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) { @@ -125,7 +136,8 @@ public String getSourceString(BibEntry entry) { BibWriter bibWriter = new BibWriter(writer, OS.NEWLINE); FieldWriter fieldWriter = FieldWriter.buildIgnoreHashes(preferences.getFieldPreferences()); try { - new BibEntryWriter(fieldWriter, entryTypesManager).write(entry, bibWriter, selectedDb.getValue().getMode()); + new BibEntryWriter(fieldWriter, entryTypesManager) + .write(entry, bibWriter, selectedDb.getValue().getMode()); } catch (IOException ioException) { return ""; } @@ -141,27 +153,31 @@ public void importEntries(List entriesToImport, boolean shouldDownload // 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()).mergeMetaData( - databaseContext.getMetaData(), - parserResult.getMetaData(), - parserResult.getPath().map(path -> path.getFileName().toString()).orElse("unknown"), - parserResult.getDatabase().getEntries()); + new DatabaseMerger(preferences.getBibEntryPreferences().getKeywordSeparator()) + .mergeStrings(databaseContext.getDatabase(), parserResult.getDatabase()); + new DatabaseMerger(preferences.getBibEntryPreferences().getKeywordSeparator()) + .mergeMetaData( + databaseContext.getMetaData(), + parserResult.getMetaData(), + parserResult + .getPath() + .map(path -> path.getFileName().toString()) + .orElse("unknown"), + parserResult.getDatabase().getEntries()); buildImportHandlerThenImportEntries(entriesToImport); } private void buildImportHandlerThenImportEntries(List entriesToImport) { - 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); } @@ -176,7 +192,8 @@ private Optional findInternalDuplicate(BibEntry entry) { if (othEntry.equals(entry)) { continue; // Don't compare the entry to itself } - if (new DuplicateCheck(entryTypesManager).isDuplicate(entry, othEntry, databaseContext.getMode())) { + if (new DuplicateCheck(entryTypesManager) + .isDuplicate(entry, othEntry, databaseContext.getMode())) { return Optional.of(othEntry); } } diff --git a/src/main/java/org/jabref/gui/importer/NewEntryAction.java b/src/main/java/org/jabref/gui/importer/NewEntryAction.java index 1da8291e9f20..ae4db2df80b7 100644 --- a/src/main/java/org/jabref/gui/importer/NewEntryAction.java +++ b/src/main/java/org/jabref/gui/importer/NewEntryAction.java @@ -1,8 +1,5 @@ package org.jabref.gui.importer; -import java.util.Optional; -import java.util.function.Supplier; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -12,10 +9,12 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.types.EntryType; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Optional; +import java.util.function.Supplier; + public class NewEntryAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(NewEntryAction.class); @@ -31,7 +30,11 @@ public class NewEntryAction extends SimpleCommand { private final GuiPreferences preferences; - public NewEntryAction(Supplier tabSupplier, DialogService dialogService, GuiPreferences preferences, StateManager stateManager) { + public NewEntryAction( + Supplier tabSupplier, + DialogService dialogService, + GuiPreferences preferences, + StateManager stateManager) { this.tabSupplier = tabSupplier; this.dialogService = dialogService; this.preferences = preferences; @@ -41,7 +44,12 @@ public NewEntryAction(Supplier tabSupplier, DialogService dialogServ this.executable.bind(ActionHelper.needsDatabase(stateManager)); } - public NewEntryAction(Supplier tabSupplier, EntryType type, DialogService dialogService, GuiPreferences preferences, StateManager stateManager) { + public NewEntryAction( + Supplier tabSupplier, + EntryType type, + DialogService dialogService, + GuiPreferences preferences, + StateManager stateManager) { this(tabSupplier, dialogService, preferences, stateManager); this.type = Optional.ofNullable(type); } @@ -56,8 +64,10 @@ public void execute() { if (type.isPresent()) { tabSupplier.get().insertEntry(new BibEntry(type.get())); } else { - EntryTypeView typeChoiceDialog = new EntryTypeView(tabSupplier.get(), dialogService, preferences); - EntryType selectedType = dialogService.showCustomDialogAndWait(typeChoiceDialog).orElse(null); + EntryTypeView typeChoiceDialog = + new EntryTypeView(tabSupplier.get(), dialogService, preferences); + EntryType selectedType = + dialogService.showCustomDialogAndWait(typeChoiceDialog).orElse(null); if (selectedType == null) { return; } diff --git a/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java b/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java index 63ae19877bdf..45b381a8f433 100644 --- a/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java +++ b/src/main/java/org/jabref/gui/importer/ParserResultWarningDialog.java @@ -1,27 +1,26 @@ package org.jabref.gui.importer; -import java.util.List; -import java.util.Objects; - import org.jabref.gui.DialogService; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; +import java.util.List; +import java.util.Objects; + /** * Class for generating a dialog showing warnings from ParserResult */ public class ParserResultWarningDialog { - private ParserResultWarningDialog() { - } + private ParserResultWarningDialog() {} /** * Shows a dialog with the warnings from an import or open of a file * * @param parserResult - ParserResult for the current import/open */ - public static void showParserResultWarningDialog(final ParserResult parserResult, - final DialogService dialogService) { + public static void showParserResultWarningDialog( + final ParserResult parserResult, final DialogService dialogService) { Objects.requireNonNull(parserResult); // Return if no warnings if (!parserResult.hasWarnings()) { @@ -42,7 +41,11 @@ public static void showParserResultWarningDialog(final ParserResult parserResult if (parserResult.getPath().isEmpty()) { dialogTitle = Localization.lang("Warnings"); } else { - dialogTitle = Localization.lang("Warnings") + " (" + parserResult.getPath().get().getFileName() + ")"; + dialogTitle = + Localization.lang("Warnings") + + " (" + + parserResult.getPath().get().getFileName() + + ")"; } // Show dialog diff --git a/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java b/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java index 68b086f4ba8a..1504cc3417ad 100644 --- a/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/CheckForNewEntryTypesAction.java @@ -1,7 +1,6 @@ package org.jabref.gui.importer.actions; -import java.util.List; -import java.util.stream.Collectors; +import com.airhacks.afterburner.injection.Injector; import org.jabref.gui.DialogService; import org.jabref.gui.importer.ImportCustomEntryTypesDialog; @@ -12,7 +11,8 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; -import com.airhacks.afterburner.injection.Injector; +import java.util.List; +import java.util.stream.Collectors; /** * This action checks whether any new custom entry types were loaded from this @@ -22,27 +22,36 @@ public class CheckForNewEntryTypesAction implements GUIPostOpenAction { @Override public boolean isActionNecessary(ParserResult parserResult, CliPreferences preferences) { - return !getListOfUnknownAndUnequalCustomizations(parserResult, preferences.getLibraryPreferences()).isEmpty(); + 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); + BibEntryTypesManager entryTypesManager = + Injector.instantiateModelOrService(BibEntryTypesManager.class); - return parserResult.getEntryTypes() - .stream() - .filter(type -> entryTypesManager.isDifferentCustomOrModifiedType(type, mode)) - .collect(Collectors.toList()); + return parserResult.getEntryTypes().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/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java index 9ccec061d234..080d75813b11 100644 --- a/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentAction.java @@ -1,13 +1,13 @@ package org.jabref.gui.importer.actions; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.preferences.CliPreferences; import org.jabref.migrations.MergeReviewIntoCommentMigration; import org.jabref.model.entry.BibEntry; +import java.util.List; + public class MergeReviewIntoCommentAction implements GUIPostOpenAction { @Override @@ -16,12 +16,15 @@ public boolean isActionNecessary(ParserResult parserResult, CliPreferences prefe } @Override - public void performAction(ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { + public void performAction( + ParserResult parserResult, DialogService dialogService, CliPreferences preferences) { MergeReviewIntoCommentMigration migration = new MergeReviewIntoCommentMigration(); 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/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java index ece4bb4e079b..fc26de573b30 100644 --- a/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java +++ b/src/main/java/org/jabref/gui/importer/actions/MergeReviewIntoCommentConfirmationDialog.java @@ -1,13 +1,13 @@ package org.jabref.gui.importer.actions; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import org.jabref.gui.DialogService; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + public class MergeReviewIntoCommentConfirmationDialog { private final DialogService dialogService; @@ -17,22 +17,27 @@ 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."); + 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") - ); + Localization.lang("Merge fields")); } } diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index d30c7d79a3de..6419c4c557eb 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -1,17 +1,5 @@ package org.jabref.gui.importer.actions; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -39,10 +27,21 @@ import org.jabref.logic.util.io.FileHistory; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + // The action concerned with opening an existing database. public class OpenDatabaseAction extends SimpleCommand { @@ -50,14 +49,15 @@ public class OpenDatabaseAction extends SimpleCommand { // List of actions that may need to be called after opening the file. Such as // upgrade actions etc. that may depend on the JabRef version that wrote the file: - private static final List POST_OPEN_ACTIONS = List.of( - // Migrations: - // Warning for migrating the Review into the Comment field - new MergeReviewIntoCommentAction(), - // Check for new custom entry types loaded from the BIB file: - new CheckForNewEntryTypesAction(), - // Migrate search groups from Search.g4 to Lucene syntax - new SearchGroupsMigrationAction()); + private static final List POST_OPEN_ACTIONS = + List.of( + // Migrations: + // Warning for migrating the Review into the Comment field + new MergeReviewIntoCommentAction(), + // Check for new custom entry types loaded from the BIB file: + new CheckForNewEntryTypesAction(), + // Migrate search groups from Search.g4 to Lucene syntax + new SearchGroupsMigrationAction()); private final LibraryTabContainer tabContainer; private final GuiPreferences preferences; @@ -70,16 +70,17 @@ public class OpenDatabaseAction extends SimpleCommand { 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; @@ -92,7 +93,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, preferences)) { action.performAction(result, dialogService, preferences); @@ -102,13 +104,15 @@ public static void performPostOpenActions(ParserResult result, DialogService dia @Override public void execute() { - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.BIBTEX_DB) - .withDefaultExtension(StandardFileType.BIBTEX_DB) - .withInitialDirectory(getInitialDirectory()) - .build(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .addExtensionFilter(StandardFileType.BIBTEX_DB) + .withDefaultExtension(StandardFileType.BIBTEX_DB) + .withInitialDirectory(getInitialDirectory()) + .build(); - List filesToOpen = dialogService.showFileOpenDialogAndGetMultipleFiles(fileDialogConfiguration); + List filesToOpen = + dialogService.showFileOpenDialogAndGetMultipleFiles(fileDialogConfiguration); openFiles(new ArrayList<>(filesToOpen)); } @@ -119,8 +123,11 @@ private Path getInitialDirectory() { if (tabContainer.getLibraryTabs().isEmpty()) { return preferences.getFilePreferences().getWorkingDirectory(); } else { - Optional databasePath = tabContainer.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); - return databasePath.map(Path::getParent).orElse(preferences.getFilePreferences().getWorkingDirectory()); + Optional databasePath = + tabContainer.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); + return databasePath + .map(Path::getParent) + .orElse(preferences.getFilePreferences().getWorkingDirectory()); } } @@ -150,7 +157,11 @@ public void openFiles(List filesToOpen) { Path file = iterator.next(); for (LibraryTab libraryTab : tabContainer.getLibraryTabs()) { if ((libraryTab.getBibDatabaseContext().getDatabasePath().isPresent()) - && libraryTab.getBibDatabaseContext().getDatabasePath().get().equals(file)) { + && libraryTab + .getBibDatabaseContext() + .getDatabasePath() + .get() + .equals(file)) { iterator.remove(); removed++; // See if we removed the final one. If so, we must perhaps @@ -168,11 +179,13 @@ public void openFiles(List filesToOpen) { // locking until the file is loaded. if (!filesToOpen.isEmpty()) { FileHistory fileHistory = preferences.getLastFilesOpenedPreferences().getFileHistory(); - filesToOpen.forEach(theFile -> { - // This method will execute the concrete file opening and loading in a background thread - openTheFile(theFile); - fileHistory.newFile(theFile); - }); + filesToOpen.forEach( + theFile -> { + // 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) { // 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: @@ -196,18 +209,19 @@ 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, - preferences, - stateManager, - tabContainer, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipboardManager, - taskExecutor); + LibraryTab newTab = + LibraryTab.createLibraryTab( + backgroundTask, + file, + dialogService, + preferences, + stateManager, + tabContainer, + fileUpdateMonitor, + entryTypesManager, + undoManager, + clipboardManager, + taskExecutor); tabContainer.addTab(newTab, true); } @@ -222,24 +236,38 @@ private ParserResult loadDatabase(Path file) throws Exception { 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(), - fileUpdateMonitor); + 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) { parserResult = ParserResult.fromError(e); @@ -247,36 +275,8 @@ private ParserResult loadDatabase(Path file) throws Exception { } if (parserResult.getDatabase().isShared()) { - 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 { - try { - new SharedDatabaseUIManager( + openSharedDatabase( + parserResult, tabContainer, dialogService, preferences, @@ -285,11 +285,45 @@ public static void openSharedDatabase(ParserResult parserResult, entryTypesManager, fileUpdateMonitor, undoManager, - clipBoardManager, - taskExecutor) + 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 { + try { + new SharedDatabaseUIManager( + tabContainer, + dialogService, + preferences, + aiService, + stateManager, + entryTypesManager, + fileUpdateMonitor, + undoManager, + clipBoardManager, + taskExecutor) .openSharedDatabaseFromParserResult(parserResult); - } catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException | - NotASharedDatabaseException e) { + } catch (SQLException + | DatabaseNotSupportedException + | InvalidDBMSConnectionPropertiesException + | NotASharedDatabaseException e) { parserResult.getDatabaseContext().clearDatabasePath(); // do not open the original file parserResult.getDatabase().clearSharedDatabaseID(); diff --git a/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java b/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java index 9e2ca086ab6b..80a3f0d2d228 100644 --- a/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/SearchGroupsMigrationAction.java @@ -1,8 +1,6 @@ package org.jabref.gui.importer.actions; -import java.nio.file.Path; -import java.util.Optional; - +import org.antlr.v4.runtime.misc.ParseCancellationException; import org.jabref.gui.DialogService; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; @@ -13,7 +11,8 @@ import org.jabref.model.groups.SearchGroup; import org.jabref.model.search.SearchFlags; -import org.antlr.v4.runtime.misc.ParseCancellationException; +import java.nio.file.Path; +import java.util.Optional; /** * This action checks whether the syntax for SearchGroups is the new one. @@ -22,13 +21,15 @@ 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"); @Override public boolean isActionNecessary(ParserResult parserResult, CliPreferences preferences) { if (parserResult.getMetaData().getGroupSearchSyntaxVersion().isPresent()) { - // Currently the presence of any version is enough to know that no migration is necessary + // Currently the presence of any version is enough to know that no migration is + // necessary return false; } @@ -49,14 +50,23 @@ 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("")), - 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"))) { + 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("")), + 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; } - parserResult.getMetaData().getGroups().ifPresent(groupTreeNode -> migrateGroups(groupTreeNode, dialogService)); + parserResult + .getMetaData() + .getGroups() + .ifPresent(groupTreeNode -> migrateGroups(groupTreeNode, dialogService)); parserResult.getMetaData().setGroupSearchSyntaxVersion(VERSION_6_0_ALPHA); parserResult.setChangedOnMigration(true); } @@ -64,13 +74,21 @@ public void performAction(ParserResult parserResult, DialogService dialogService private void migrateGroups(GroupTreeNode node, DialogService dialogService) { if (node.getGroup() instanceof SearchGroup searchGroup) { try { - String luceneSearchExpression = SearchToLuceneMigration.migrateToLuceneSyntax(searchGroup.getSearchExpression(), searchGroup.getSearchFlags().contains(SearchFlags.REGULAR_EXPRESSION)); + String luceneSearchExpression = + SearchToLuceneMigration.migrateToLuceneSyntax( + searchGroup.getSearchExpression(), + searchGroup + .getSearchFlags() + .contains(SearchFlags.REGULAR_EXPRESSION)); searchGroup.setSearchExpression(luceneSearchExpression); } catch (ParseCancellationException e) { - Optional luceneSearchExpression = dialogService.showInputDialogWithDefaultAndWait( - Localization.lang("Search group migration failed"), - Localization.lang("The search group '%0' could not be migrated. Please enter the new search expression.", searchGroup.getName()), - searchGroup.getSearchExpression()); + Optional luceneSearchExpression = + dialogService.showInputDialogWithDefaultAndWait( + Localization.lang("Search group migration failed"), + Localization.lang( + "The search group '%0' could not be migrated. Please enter the new search expression.", + searchGroup.getName()), + searchGroup.getSearchExpression()); luceneSearchExpression.ifPresent(searchGroup::setSearchExpression); } } diff --git a/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java b/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java index 953020e6c61e..0ad228acdf70 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/LookupIdentifierAction.java @@ -1,9 +1,7 @@ package org.jabref.gui.importer.fetcher; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -21,12 +19,13 @@ import org.jabref.model.FieldChange; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.identifier.Identifier; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; public class LookupIdentifierAction extends SimpleCommand { @@ -38,27 +37,34 @@ public class LookupIdentifierAction extends SimpleCommand 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; this.dialogService = dialogService; 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.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."))); } @Override public void execute() { try { BackgroundTask.wrap(() -> lookupIdentifiers(stateManager.getSelectedEntries())) - .onSuccess(dialogService::notify) - .executeWith(taskExecutor); + .onSuccess(dialogService::notify) + .executeWith(taskExecutor); } catch (Exception e) { LOGGER.error("Problem running ID Worker", e); } @@ -70,13 +76,19 @@ public Action getAction() { private String lookupIdentifiers(List bibEntries) { String totalCount = Integer.toString(bibEntries.size()); - NamedCompound namedCompound = new NamedCompound(Localization.lang("Look up %0", fetcher.getIdentifierName())); + NamedCompound namedCompound = + new NamedCompound(Localization.lang("Look up %0", fetcher.getIdentifierName())); int count = 0; int foundCount = 0; for (BibEntry bibEntry : bibEntries) { count++; - final String statusMessage = Localization.lang("Looking up %0... - entry %1 out of %2 - found %3", - fetcher.getIdentifierName(), Integer.toString(count), totalCount, Integer.toString(foundCount)); + final String statusMessage = + 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(statusMessage)); Optional identifier = Optional.empty(); try { @@ -85,12 +97,20 @@ private String lookupIdentifiers(List bibEntries) { LOGGER.error("Could not fetch {}", fetcher.getIdentifierName(), e); } if (identifier.isPresent() && !bibEntry.hasField(identifier.get().getDefaultField())) { - Optional fieldChange = bibEntry.setField(identifier.get().getDefaultField(), identifier.get().getNormalized()); + Optional fieldChange = + bibEntry.setField( + identifier.get().getDefaultField(), + identifier.get().getNormalized()); 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 +119,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/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java index d0da1c301024..bfdfbff83128 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneView.java @@ -1,5 +1,7 @@ package org.jabref.gui.importer.fetcher; +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.binding.BooleanExpression; import javafx.css.PseudoClass; import javafx.scene.control.Button; @@ -24,8 +26,6 @@ import org.jabref.logic.importer.SearchBasedFetcher; import org.jabref.logic.l10n.Localization; -import com.tobiasdiez.easybind.EasyBind; - public class WebSearchPaneView extends VBox { private static final PseudoClass QUERY_INVALID = PseudoClass.getPseudoClass("invalid"); @@ -34,7 +34,8 @@ public class WebSearchPaneView extends VBox { private final GuiPreferences preferences; private final DialogService dialogService; - public WebSearchPaneView(GuiPreferences preferences, DialogService dialogService, StateManager stateManager) { + public WebSearchPaneView( + GuiPreferences preferences, DialogService dialogService, StateManager stateManager) { this.preferences = preferences; this.dialogService = dialogService; this.viewModel = new WebSearchPaneViewModel(preferences, dialogService, stateManager); @@ -69,18 +70,27 @@ private void initialize() { * Allows triggering search on pressing enter */ private void enableEnterToTriggerSearch(TextField query) { - query.setOnKeyPressed(event -> { - if (event.getCode() == KeyCode.ENTER) { - viewModel.search(); - } - }); + query.setOnKeyPressed( + event -> { + if (event.getCode() == KeyCode.ENTER) { + viewModel.search(); + } + }); } private void addQueryValidationHints(TextField query) { - EasyBind.subscribe(viewModel.queryValidationStatus().validProperty(), + EasyBind.subscribe( + viewModel.queryValidationStatus().validProperty(), valid -> { - if (!valid && viewModel.queryValidationStatus().getHighestMessage().isPresent()) { - query.setTooltip(new Tooltip(viewModel.queryValidationStatus().getHighestMessage().get().getMessage())); + if (!valid + && viewModel.queryValidationStatus().getHighestMessage().isPresent()) { + query.setTooltip( + new Tooltip( + viewModel + .queryValidationStatus() + .getHighestMessage() + .get() + .getMessage())); query.pseudoClassStateChanged(QUERY_INVALID, true); } else { query.setTooltip(null); @@ -93,7 +103,8 @@ private void addQueryValidationHints(TextField query) { * Create button that triggers search */ private Button createSearchButton() { - BooleanExpression importerEnabled = preferences.getImporterPreferences().importerEnabledProperty(); + BooleanExpression importerEnabled = + preferences.getImporterPreferences().importerEnabledProperty(); Button search = new Button(Localization.lang("Search")); search.setDefaultButton(false); search.setOnAction(event -> viewModel.search()); @@ -108,14 +119,22 @@ private Button createSearchButton() { private StackPane createHelpButtonContainer() { StackPane helpButtonContainer = new StackPane(); 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())); - helpButtonContainer.getChildren().setAll(helpButton); - } else { - helpButtonContainer.getChildren().clear(); - } - }); + 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())); + helpButtonContainer.getChildren().setAll(helpButton); + } else { + helpButtonContainer.getChildren().clear(); + } + }); return helpButtonContainer; } } diff --git a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java index 9057d8078b86..42f8b3e482eb 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java @@ -1,6 +1,13 @@ package org.jabref.gui.importer.fetcher; -import java.util.concurrent.Callable; +import static org.jabref.logic.importer.fetcher.transformers.AbstractQueryTransformer.NO_EXPLICIT_FIELD; + +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -11,6 +18,10 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import org.apache.lucene.queryparser.flexible.core.QueryNodeParseException; +import org.apache.lucene.queryparser.flexible.core.parser.SyntaxParser; +import org.apache.lucene.queryparser.flexible.standard.parser.ParseException; +import org.apache.lucene.queryparser.flexible.standard.parser.StandardSyntaxParser; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.frame.SidePanePreferences; @@ -25,22 +36,13 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.OptionalUtil; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; -import org.apache.lucene.queryparser.flexible.core.QueryNodeParseException; -import org.apache.lucene.queryparser.flexible.core.parser.SyntaxParser; -import org.apache.lucene.queryparser.flexible.standard.parser.ParseException; -import org.apache.lucene.queryparser.flexible.standard.parser.StandardSyntaxParser; - -import static org.jabref.logic.importer.fetcher.transformers.AbstractQueryTransformer.NO_EXPLICIT_FIELD; +import java.util.concurrent.Callable; 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; @@ -49,14 +51,16 @@ public class WebSearchPaneViewModel { private final Validator searchQueryValidator; private final SyntaxParser parser = new StandardSyntaxParser(); - public WebSearchPaneViewModel(GuiPreferences preferences, DialogService dialogService, StateManager stateManager) { + public WebSearchPaneViewModel( + GuiPreferences preferences, DialogService dialogService, StateManager stateManager) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; - fetchers.setAll(WebFetchers.getSearchBasedFetchers( - preferences.getImportFormatPreferences(), - preferences.getImporterPreferences())); + fetchers.setAll( + WebFetchers.getSearchBasedFetchers( + preferences.getImportFormatPreferences(), + preferences.getImporterPreferences())); // Choose last-selected fetcher as default SidePanePreferences sidePanePreferences = preferences.getSidePanePreferences(); @@ -66,39 +70,48 @@ public WebSearchPaneViewModel(GuiPreferences preferences, DialogService dialogSe } else { selectedFetcherProperty().setValue(fetchers.get(defaultFetcherIndex)); } - EasyBind.subscribe(selectedFetcherProperty(), newFetcher -> { - int newIndex = fetchers.indexOf(newFetcher); - 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(""); - } + EasyBind.subscribe( + selectedFetcherProperty(), + newFetcher -> { + int newIndex = fetchers.indexOf(newFetcher); + 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(""); + } + }); } public ObservableList getFetchers() { @@ -139,7 +152,8 @@ public void search() { return; } if (stateManager.getActiveDatabase().isEmpty()) { - dialogService.notify(Localization.lang("Please open or start a new library before searching")); + dialogService.notify( + Localization.lang("Please open or start a new library before searching")); return; } @@ -150,19 +164,27 @@ public void search() { String fetcherName = activeFetcher.getName(); if (CompositeIdFetcher.containsValidId(query)) { - CompositeIdFetcher compositeIdFetcher = new CompositeIdFetcher(preferences.getImportFormatPreferences()); - parserResultCallable = () -> new ParserResult(OptionalUtil.toList(compositeIdFetcher.performSearchById(query))); + CompositeIdFetcher compositeIdFetcher = + new CompositeIdFetcher(preferences.getImportFormatPreferences()); + 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)" + // 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)); + BackgroundTask task = + BackgroundTask.wrap(parserResultCallable) + .withInitialMessage(Localization.lang("Processing \"%0\"...", query)); task.onFailure(dialogService::showErrorDialogAndWait); - ImportEntriesDialog dialog = new ImportEntriesDialog(stateManager.getActiveDatabase().get(), task); + ImportEntriesDialog dialog = + new ImportEntriesDialog(stateManager.getActiveDatabase().get(), task); dialog.setTitle(fetcherName); dialogService.showCustomDialogAndWait(dialog); } diff --git a/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java b/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java index 506dee530fba..6ee267bba854 100644 --- a/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java +++ b/src/main/java/org/jabref/gui/integrity/IntegrityCheckAction.java @@ -1,8 +1,6 @@ package org.jabref.gui.integrity; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import javafx.collections.ObservableList; import javafx.concurrent.Task; @@ -20,7 +18,9 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; public class IntegrityCheckAction extends SimpleCommand { @@ -31,12 +31,13 @@ public class IntegrityCheckAction extends SimpleCommand { 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,40 +49,52 @@ 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, - preferences.getEntryEditorPreferences().shouldAllowIntegerEditionBibtex()); + 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<>() { - @Override - protected List call() { - ObservableList entries = database.getDatabase().getEntries(); - List result = new ArrayList<>(check.checkDatabase(database.getDatabase())); - for (int i = 0; i < entries.size(); i++) { - if (isCancelled()) { - break; - } + Task> task = + new Task<>() { + @Override + protected List call() { + ObservableList entries = database.getDatabase().getEntries(); + List result = + new ArrayList<>(check.checkDatabase(database.getDatabase())); + for (int i = 0; i < entries.size(); i++) { + if (isCancelled()) { + break; + } - BibEntry entry = entries.get(i); - result.addAll(check.checkEntry(entry)); - updateProgress(i, entries.size()); - } + BibEntry entry = entries.get(i); + result.addAll(check.checkEntry(entry)); + updateProgress(i, entries.size()); + } - return result; - } - }; - task.setOnSucceeded(value -> { - List messages = task.getValue(); - if (messages.isEmpty()) { - dialogService.notify(Localization.lang("No problems found.")); - } else { - dialogService.showCustomDialogAndWait(new IntegrityCheckDialog(messages, tabSupplier.get())); - } - }); - task.setOnFailed(event -> dialogService.showErrorDialogAndWait("Integrity check failed.", task.getException())); + return result; + } + }; + task.setOnSucceeded( + value -> { + List messages = task.getValue(); + if (messages.isEmpty()) { + dialogService.notify(Localization.lang("No problems found.")); + } else { + dialogService.showCustomDialogAndWait( + new IntegrityCheckDialog(messages, tabSupplier.get())); + } + }); + task.setOnFailed( + event -> + dialogService.showErrorDialogAndWait( + "Integrity check failed.", task.getException())); dialogService.showProgressDialog( Localization.lang("Checking integrity..."), diff --git a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java index 00b3cc32bc91..3254f480ce38 100644 --- a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java +++ b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialog.java @@ -1,7 +1,8 @@ package org.jabref.gui.integrity; -import java.util.List; -import java.util.function.Function; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.ListChangeListener; @@ -13,6 +14,7 @@ import javafx.scene.input.MouseButton; import javafx.stage.Modality; +import org.controlsfx.control.table.TableFilter; import org.jabref.gui.LibraryTab; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BaseDialog; @@ -20,9 +22,8 @@ import org.jabref.logic.integrity.IntegrityMessage; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; -import org.controlsfx.control.table.TableFilter; +import java.util.List; +import java.util.function.Function; public class IntegrityCheckDialog extends BaseDialog { @@ -47,17 +48,19 @@ 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()); } private void onSelectionChanged(ListChangeListener.Change change) { if (change.next()) { - change.getAddedSubList().stream().findFirst().ifPresent(message -> - libraryTab.editEntryAndFocusField(message.entry(), message.field())); + change.getAddedSubList().stream() + .findFirst() + .ifPresent( + message -> + libraryTab.editEntryAndFocusField( + message.entry(), message.field())); } } @@ -71,49 +74,64 @@ private void initialize() { messagesTable.getSelectionModel().getSelectedItems().addListener(this::onSelectionChanged); messagesTable.setItems(viewModel.getMessages()); - 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())); + 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); - tableFilter = TableFilter.forTableView(messagesTable) - .apply(); + tableFilter = TableFilter.forTableView(messagesTable).apply(); addMessageColumnFilter(keyColumn, keyFilterButton); addMessageColumnFilter(fieldColumn, fieldFilterButton); addMessageColumnFilter(messageColumn, messageFilterButton); } - private void addMessageColumnFilter(TableColumn messageColumn, MenuButton messageFilterButton) { - tableFilter.getColumnFilter(messageColumn).ifPresent(columnFilter -> { - ContextMenu messageContextMenu = messageColumn.getContextMenu(); - if (messageContextMenu != null) { - messageFilterButton.setContextMenu(messageContextMenu); - messageFilterButton.setOnMouseClicked(event -> { - if (event.getButton() == MouseButton.PRIMARY) { - if (messageContextMenu.isShowing()) { - messageContextMenu.setX(event.getScreenX()); - messageContextMenu.setY(event.getScreenY()); - } else { - messageContextMenu.show(messageFilterButton, event.getScreenX(), event.getScreenY()); - } - } - }); - } - }); + private void addMessageColumnFilter( + TableColumn messageColumn, MenuButton messageFilterButton) { + tableFilter + .getColumnFilter(messageColumn) + .ifPresent( + columnFilter -> { + ContextMenu messageContextMenu = messageColumn.getContextMenu(); + if (messageContextMenu != null) { + messageFilterButton.setContextMenu(messageContextMenu); + messageFilterButton.setOnMouseClicked( + event -> { + if (event.getButton() == MouseButton.PRIMARY) { + if (messageContextMenu.isShowing()) { + messageContextMenu.setX(event.getScreenX()); + messageContextMenu.setY(event.getScreenY()); + } else { + messageContextMenu.show( + messageFilterButton, + event.getScreenX(), + event.getScreenY()); + } + } + }); + } + }); } public void clearFilters() { if (tableFilter != null) { tableFilter.resetFilter(); - messagesTable.getColumns().forEach(column -> { - tableFilter.selectAllValues(column); - column.setGraphic(null); - }); + messagesTable + .getColumns() + .forEach( + column -> { + tableFilter.selectAllValues(column); + column.setGraphic(null); + }); } } } diff --git a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java index 1313417642ef..0e3c83c643c9 100644 --- a/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java +++ b/src/main/java/org/jabref/gui/integrity/IntegrityCheckDialogViewModel.java @@ -1,13 +1,13 @@ package org.jabref.gui.integrity; -import java.util.List; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jabref.gui.AbstractViewModel; import org.jabref.logic.integrity.IntegrityMessage; +import java.util.List; + public class IntegrityCheckDialogViewModel extends AbstractViewModel { private final ObservableList messages; diff --git a/src/main/java/org/jabref/gui/journals/AbbreviateAction.java b/src/main/java/org/jabref/gui/journals/AbbreviateAction.java index 528c62688cf8..1b05cd37573e 100644 --- a/src/main/java/org/jabref/gui/journals/AbbreviateAction.java +++ b/src/main/java/org/jabref/gui/journals/AbbreviateAction.java @@ -1,15 +1,5 @@ package org.jabref.gui.journals; -import java.util.List; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -26,10 +16,19 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.FieldFactory; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + /** * Converts journal full names to either iso or medline abbreviations for all selected entries. */ @@ -48,14 +47,15 @@ public class AbbreviateAction extends SimpleCommand { 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; @@ -81,47 +81,84 @@ public void execute() { || (action == StandardActions.ABBREVIATE_DOTLESS) || (action == StandardActions.ABBREVIATE_SHORTEST_UNIQUE)) { dialogService.notify(Localization.lang("Abbreviating...")); - stateManager.getActiveDatabase().ifPresent(databaseContext -> - BackgroundTask.wrap(() -> abbreviate(stateManager.getActiveDatabase().get(), stateManager.getSelectedEntries())) - .onSuccess(dialogService::notify) - .executeWith(taskExecutor)); + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> + 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(databaseContext -> - BackgroundTask.wrap(() -> unabbreviate(stateManager.getActiveDatabase().get(), stateManager.getSelectedEntries())) - .onSuccess(dialogService::notify) - .executeWith(taskExecutor)); + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> + 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, - journalAbbreviationPreferences.shouldUseFJournalField()); + UndoableAbbreviator undoableAbbreviator = + new UndoableAbbreviator( + abbreviationRepository, + abbreviationType, + journalAbbreviationPreferences.shouldUseFJournalField()); NamedCompound ce = new NamedCompound(Localization.lang("Abbreviate journal names")); // Collect all callables to execute in one collection. - Set> tasks = entries.stream().>map(entry -> () -> - FieldFactory.getJournalNameFields().stream().anyMatch(journalField -> - undoableAbbreviator.abbreviate(databaseContext.getDatabase(), entry, journalField, ce))) - .collect(Collectors.toSet()); + Set> tasks = + entries.stream() + .>map( + entry -> + () -> + FieldFactory.getJournalNameFields().stream() + .anyMatch( + journalField -> + undoableAbbreviator + .abbreviate( + databaseContext + .getDatabase(), + entry, + journalField, + ce))) + .collect(Collectors.toSet()); // Execute the callables and wait for the results. List> futures = HeadlessExecutorService.INSTANCE.executeAll(tasks); // Evaluate the results of the callables. - long count = futures.stream().filter(future -> { - try { - return future.get(); - } catch (InterruptedException | ExecutionException exception) { - LOGGER.error("Unable to retrieve value.", exception); - return false; - } - }).count(); + long count = + futures.stream() + .filter( + future -> { + try { + return future.get(); + } catch (InterruptedException | ExecutionException exception) { + LOGGER.error("Unable to retrieve value.", exception); + return false; + } + }) + .count(); if (count == 0) { return Localization.lang("No journal names could be abbreviated."); @@ -134,12 +171,27 @@ private String abbreviate(BibDatabaseContext databaseContext, List ent } private String unabbreviate(BibDatabaseContext databaseContext, List entries) { - UndoableUnabbreviator undoableAbbreviator = new UndoableUnabbreviator(abbreviationRepository); + 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."); } diff --git a/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java b/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java index f330a30f0e80..b2239d661603 100644 --- a/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java +++ b/src/main/java/org/jabref/gui/journals/UndoableAbbreviator.java @@ -1,7 +1,5 @@ package org.jabref.gui.journals; -import javax.swing.undo.CompoundEdit; - import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.logic.journals.Abbreviation; import org.jabref.logic.journals.JournalAbbreviationRepository; @@ -11,6 +9,8 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import javax.swing.undo.CompoundEdit; + // Undo redo stuff public class UndoableAbbreviator { @@ -18,7 +18,10 @@ public class UndoableAbbreviator { 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; @@ -33,7 +36,8 @@ public UndoableAbbreviator(JournalAbbreviationRepository journalAbbreviationRepo * @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) { + public boolean abbreviate( + BibDatabase database, BibEntry entry, Field fieldName, CompoundEdit ce) { if (!entry.hasField(fieldName)) { return false; } @@ -56,9 +60,13 @@ public boolean abbreviate(BibDatabase database, BibEntry entry, Field fieldName, } // Store full name into fjournal but only if it exists - if (useFJournalField && (StandardField.JOURNAL == fieldName || StandardField.JOURNALTITLE == fieldName)) { + if (useFJournalField + && (StandardField.JOURNAL == fieldName + || StandardField.JOURNALTITLE == fieldName)) { entry.setField(AMSField.FJOURNAL, abbreviation.getName()); - ce.addEdit(new UndoableFieldChange(entry, AMSField.FJOURNAL, null, abbreviation.getName())); + ce.addEdit( + new UndoableFieldChange( + entry, AMSField.FJOURNAL, null, abbreviation.getName())); } entry.setField(fieldName, newText); @@ -68,14 +76,12 @@ 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(); + case DEFAULT -> text.getAbbreviation(); + case DOTLESS -> text.getDotlessAbbreviation(); + case SHORTEST_UNIQUE -> text.getShortestUniqueAbbreviation(); default -> - throw new IllegalStateException("Unexpected value: %s".formatted(abbreviationType)); + throw new IllegalStateException( + "Unexpected value: %s".formatted(abbreviationType)); }; } } diff --git a/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java b/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java index acb101aef583..1be8795d7f6c 100644 --- a/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java +++ b/src/main/java/org/jabref/gui/journals/UndoableUnabbreviator.java @@ -1,7 +1,5 @@ package org.jabref.gui.journals; -import javax.swing.undo.CompoundEdit; - import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.logic.journals.Abbreviation; import org.jabref.logic.journals.JournalAbbreviationRepository; @@ -11,6 +9,8 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import javax.swing.undo.CompoundEdit; + public class UndoableUnabbreviator { private final JournalAbbreviationRepository journalAbbreviationRepository; @@ -27,7 +27,8 @@ public UndoableUnabbreviator(JournalAbbreviationRepository journalAbbreviationRe * @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) { + public boolean unabbreviate( + BibDatabase database, BibEntry entry, Field field, CompoundEdit ce) { if (!entry.hasField(field)) { return false; } @@ -58,7 +59,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; } diff --git a/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java index 8c199b02c971..4245f3ccbc8a 100644 --- a/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java +++ b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java @@ -2,110 +2,120 @@ import javafx.scene.input.KeyEvent; -import org.jabref.logic.util.strings.StringManipulator; -import org.jabref.model.util.ResultingStringState; - import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.NavigationActions; +import org.jabref.logic.util.strings.StringManipulator; +import org.jabref.model.util.ResultingStringState; public class CodeAreaKeyBindings { - public static void call(CodeArea codeArea, KeyEvent event, KeyBindingRepository keyBindingRepository) { - keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { - switch (binding) { - case EDITOR_DELETE -> { - codeArea.deleteNextChar(); - event.consume(); - } - case EDITOR_BACKWARD -> { - codeArea.previousChar(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_FORWARD -> { - codeArea.nextChar(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_WORD_BACKWARD -> { - codeArea.wordBreaksBackwards(2, NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_WORD_FORWARD -> { - codeArea.wordBreaksForwards(2, NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_BEGINNING_DOC -> { - codeArea.start(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_UP -> { - codeArea.paragraphStart(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_BEGINNING -> { - codeArea.lineStart(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_END_DOC -> { - codeArea.end(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_DOWN -> { - codeArea.paragraphEnd(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_END -> { - codeArea.lineEnd(NavigationActions.SelectionPolicy.CLEAR); - event.consume(); - } - case EDITOR_CAPITALIZE -> { - int pos = codeArea.getCaretPosition(); - String text = codeArea.getText(0, codeArea.getText().length()); - ResultingStringState res = StringManipulator.capitalize(pos, text); - codeArea.replaceText(res.text); - codeArea.displaceCaret(res.caretPosition); - event.consume(); - } - case EDITOR_LOWERCASE -> { - int pos = codeArea.getCaretPosition(); - String text = codeArea.getText(0, codeArea.getText().length()); - ResultingStringState res = StringManipulator.lowercase(pos, text); - codeArea.replaceText(res.text); - codeArea.displaceCaret(res.caretPosition); - event.consume(); - } - case EDITOR_UPPERCASE -> { - int pos = codeArea.getCaretPosition(); - String text = codeArea.getText(0, codeArea.getText().length()); - ResultingStringState res = StringManipulator.uppercase(pos, text); - codeArea.clear(); - codeArea.replaceText(res.text); - codeArea.displaceCaret(res.caretPosition); - event.consume(); - } - case EDITOR_KILL_LINE -> { - int pos = codeArea.getCaretPosition(); - codeArea.replaceText(codeArea.getText(0, pos)); - codeArea.displaceCaret(pos); - event.consume(); - } - case EDITOR_KILL_WORD -> { - int pos = codeArea.getCaretPosition(); - String text = codeArea.getText(0, codeArea.getText().length()); - ResultingStringState res = StringManipulator.killWord(pos, text); - codeArea.replaceText(res.text); - codeArea.displaceCaret(res.caretPosition); - event.consume(); - } - case EDITOR_KILL_WORD_BACKWARD -> { - int pos = codeArea.getCaretPosition(); - String text = codeArea.getText(0, codeArea.getText().length()); - ResultingStringState res = StringManipulator.backwardKillWord(pos, text); - codeArea.replaceText(res.text); - codeArea.displaceCaret(res.caretPosition); - event.consume(); - } - } - }); + public static void call( + CodeArea codeArea, KeyEvent event, KeyBindingRepository keyBindingRepository) { + keyBindingRepository + .mapToKeyBinding(event) + .ifPresent( + binding -> { + switch (binding) { + case EDITOR_DELETE -> { + codeArea.deleteNextChar(); + event.consume(); + } + case EDITOR_BACKWARD -> { + codeArea.previousChar(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_FORWARD -> { + codeArea.nextChar(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_WORD_BACKWARD -> { + codeArea.wordBreaksBackwards( + 2, NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_WORD_FORWARD -> { + codeArea.wordBreaksForwards( + 2, NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_BEGINNING_DOC -> { + codeArea.start(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_UP -> { + codeArea.paragraphStart( + NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_BEGINNING -> { + codeArea.lineStart(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_END_DOC -> { + codeArea.end(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_DOWN -> { + codeArea.paragraphEnd(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_END -> { + codeArea.lineEnd(NavigationActions.SelectionPolicy.CLEAR); + event.consume(); + } + case EDITOR_CAPITALIZE -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = + StringManipulator.capitalize(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_LOWERCASE -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = + StringManipulator.lowercase(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_UPPERCASE -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = + StringManipulator.uppercase(pos, text); + codeArea.clear(); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_LINE -> { + int pos = codeArea.getCaretPosition(); + codeArea.replaceText(codeArea.getText(0, pos)); + codeArea.displaceCaret(pos); + event.consume(); + } + case EDITOR_KILL_WORD -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = + StringManipulator.killWord(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_WORD_BACKWARD -> { + int pos = codeArea.getCaretPosition(); + String text = codeArea.getText(0, codeArea.getText().length()); + ResultingStringState res = + StringManipulator.backwardKillWord(pos, text); + codeArea.replaceText(res.text); + codeArea.displaceCaret(res.caretPosition); + event.consume(); + } + } + }); } } - diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index d0cb61003387..2fdddb4db784 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -8,126 +8,456 @@ 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_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_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_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_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_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), - 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), + 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), ACCEPT("Accept", Localization.lang("Accept"), "ctrl+ENTER", KeyBindingCategory.EDIT), - 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), + 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), CLEANUP("Cleanup", Localization.lang("Cleanup entries"), "alt+F8", KeyBindingCategory.QUALITY), - CLOSE_DATABASE("Close library", Localization.lang("Close library"), "ctrl+W", KeyBindingCategory.FILE), + 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), + 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_PREVIEW("Copy preview", Localization.lang("Copy preview"), "ctrl+shift+C", KeyBindingCategory.VIEW), + 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), - 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), + // 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), EXPORT("Export", Localization.lang("Export"), "ctrl+alt+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), - 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), + 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), + 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), - MERGE_ENTRIES("Merge entries", Localization.lang("Merge entries"), "ctrl+M", KeyBindingCategory.TOOLS), + 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), - NEW_ARTICLE("New article", Localization.lang("New article"), "ctrl+shift+A", 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_ENTRY("New entry", Localization.lang("New entry"), "ctrl+N", KeyBindingCategory.BIBTEX), - NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE("New entry from plain text", Localization.lang("New entry from plain text (online)"), "ctrl+shift+N", 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_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), - NEW_TECHREPORT("New technical report", Localization.lang("New technical report"), "", KeyBindingCategory.BIBTEX), - NEW_INPROCEEDINGS("New inproceesings", Localization.lang("New inproceedings"), "", KeyBindingCategory.BIBTEX), + NEW_ENTRY_FROM_PLAIN_TEXT_ONLINE( + "New entry from plain text", + Localization.lang("New entry from plain text (online)"), + "ctrl+shift+N", + 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_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), + NEW_TECHREPORT( + "New technical report", + Localization.lang("New technical report"), + "", + KeyBindingCategory.BIBTEX), + NEW_INPROCEEDINGS( + "New inproceesings", + Localization.lang("New inproceedings"), + "", + KeyBindingCategory.BIBTEX), - 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_DATABASE("Open library", Localization.lang("Open library"), "ctrl+O", KeyBindingCategory.FILE), + 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_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_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), 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), + 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), - 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), + 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), 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( + "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), 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), - UNABBREVIATE("Unabbreviate", Localization.lang("Unabbreviate"), "ctrl+alt+shift+A", KeyBindingCategory.TOOLS), + 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), + 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), - 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); + 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); 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) { + KeyBinding( + String constantName, + String localization, + String defaultKeyBinding, + KeyBindingCategory category) { this.constant = constantName; this.localization = localization; this.defaultBinding = defaultKeyBinding; diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java b/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java index ae8cb0b673f1..04e28765c11b 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingCategory.java @@ -4,7 +4,6 @@ import org.jabref.model.database.BibDatabaseMode; public enum KeyBindingCategory { - FILE(Localization.lang("File")), EDIT(Localization.lang("Edit")), SEARCH(Localization.lang("Search")), diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java b/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java index 82183dd74be1..a039d1685a0b 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBindingRepository.java @@ -1,5 +1,14 @@ package org.jabref.gui.keyboard; +import javafx.beans.property.MapProperty; +import javafx.beans.property.SimpleMapProperty; +import javafx.collections.FXCollections; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; + +import org.jabref.logic.os.OS; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -11,15 +20,6 @@ import java.util.TreeMap; import java.util.stream.Collectors; -import javafx.beans.property.MapProperty; -import javafx.beans.property.SimpleMapProperty; -import javafx.collections.FXCollections; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; - -import org.jabref.logic.os.OS; - /** * Handles keyboard shortcuts. Including checking whether a keybinding matches. * See {@link #matches}. @@ -40,9 +40,14 @@ public KeyBindingRepository(SortedMap bindings) { } public KeyBindingRepository(List bindNames, List bindings) { - this.bindings = new SimpleMapProperty<>(FXCollections.observableMap(new TreeMap<>(Comparator.comparing(KeyBinding::getLocalization)))); - - if ((bindNames.isEmpty()) || (bindings.isEmpty()) || (bindNames.size() != bindings.size())) { + 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()); @@ -61,7 +66,8 @@ public KeyBindingRepository(List bindNames, List bindings) { * @param keyEvent as KeEvent * @return true if matching, else false */ - public static boolean checkKeyCombinationEquality(KeyCombination combination, KeyEvent keyEvent) { + public static boolean checkKeyCombinationEquality( + KeyCombination combination, KeyEvent keyEvent) { KeyCode code = keyEvent.getCode(); if (code == KeyCode.UNDEFINED) { return false; @@ -103,7 +109,9 @@ public void put(String key, String value) { } private Optional getKeyBinding(String key) { - return Arrays.stream(KeyBinding.values()).filter(b -> b.getConstant().equals(key)).findFirst(); + return Arrays.stream(KeyBinding.values()) + .filter(b -> b.getConstant().equals(key)) + .findFirst(); } public void resetToDefault(String key) { @@ -137,8 +145,8 @@ 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()); } /** @@ -147,9 +155,7 @@ private Set mapToKeyBindings(KeyEvent keyEvent) { * 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) { @@ -171,8 +177,9 @@ public Optional getKeyCombination(KeyBinding bindName) { * @return true if matching, else false */ public boolean checkKeyCombinationEquality(KeyBinding binding, KeyEvent keyEvent) { - return getKeyCombination(binding).filter(combination -> checkKeyCombinationEquality(combination, keyEvent)) - .isPresent(); + return getKeyCombination(binding) + .filter(combination -> checkKeyCombinationEquality(combination, keyEvent)) + .isPresent(); } public List getBindNames() { diff --git a/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java b/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java index 7140fefe4a62..5238fbb52faa 100644 --- a/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java +++ b/src/main/java/org/jabref/gui/keyboard/TextInputKeyBindings.java @@ -9,87 +9,107 @@ public class TextInputKeyBindings { - public static void call(Scene scene, KeyEvent event, KeyBindingRepository keyBindingRepository) { + public static void call( + Scene scene, KeyEvent event, KeyBindingRepository keyBindingRepository) { if (scene.focusOwnerProperty().get() instanceof TextInputControl focusedTextField) { - keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { - switch (binding) { - case EDITOR_DELETE -> { - focusedTextField.deleteNextChar(); - event.consume(); - } - case EDITOR_BACKWARD -> { - focusedTextField.backward(); - event.consume(); - } - case EDITOR_FORWARD -> { - focusedTextField.forward(); - event.consume(); - } - case EDITOR_WORD_BACKWARD -> { - focusedTextField.previousWord(); - event.consume(); - } - case EDITOR_WORD_FORWARD -> { - focusedTextField.nextWord(); - event.consume(); - } - case EDITOR_BEGINNING, EDITOR_UP, EDITOR_BEGINNING_DOC -> { - focusedTextField.home(); - event.consume(); - } - case EDITOR_END, EDITOR_DOWN, EDITOR_END_DOC -> { - focusedTextField.end(); - event.consume(); - } - case EDITOR_CAPITALIZE -> { - int pos = focusedTextField.getCaretPosition(); - String text = focusedTextField.getText(0, focusedTextField.getText().length()); - ResultingStringState res = StringManipulator.capitalize(pos, text); - focusedTextField.setText(res.text); - focusedTextField.positionCaret(res.caretPosition); - event.consume(); - } - case EDITOR_LOWERCASE -> { - int pos = focusedTextField.getCaretPosition(); - String text = focusedTextField.getText(0, focusedTextField.getText().length()); - ResultingStringState res = StringManipulator.lowercase(pos, text); - focusedTextField.setText(res.text); - focusedTextField.positionCaret(res.caretPosition); - event.consume(); - } - case EDITOR_UPPERCASE -> { - int pos = focusedTextField.getCaretPosition(); - String text = focusedTextField.getText(0, focusedTextField.getText().length()); - ResultingStringState res = StringManipulator.uppercase(pos, text); - focusedTextField.setText(res.text); - focusedTextField.positionCaret(res.caretPosition); - event.consume(); - } - case EDITOR_KILL_LINE -> { - int pos = focusedTextField.getCaretPosition(); - focusedTextField.setText(focusedTextField.getText(0, pos)); - focusedTextField.positionCaret(pos); - event.consume(); - } - case EDITOR_KILL_WORD -> { - int pos = focusedTextField.getCaretPosition(); - String text = focusedTextField.getText(0, focusedTextField.getText().length()); - ResultingStringState res = StringManipulator.killWord(pos, text); - focusedTextField.setText(res.text); - focusedTextField.positionCaret(res.caretPosition); - event.consume(); - } - case EDITOR_KILL_WORD_BACKWARD -> { - int pos = focusedTextField.getCaretPosition(); - String text = focusedTextField.getText(0, focusedTextField.getText().length()); - ResultingStringState res = StringManipulator.backwardKillWord(pos, text); - focusedTextField.setText(res.text); - focusedTextField.positionCaret(res.caretPosition); - event.consume(); - } - // CLEAR_SEARCH is handled at org.jabref.gui.search.SearchTextField - } - }); + keyBindingRepository + .mapToKeyBinding(event) + .ifPresent( + binding -> { + switch (binding) { + case EDITOR_DELETE -> { + focusedTextField.deleteNextChar(); + event.consume(); + } + case EDITOR_BACKWARD -> { + focusedTextField.backward(); + event.consume(); + } + case EDITOR_FORWARD -> { + focusedTextField.forward(); + event.consume(); + } + case EDITOR_WORD_BACKWARD -> { + focusedTextField.previousWord(); + event.consume(); + } + case EDITOR_WORD_FORWARD -> { + focusedTextField.nextWord(); + event.consume(); + } + case EDITOR_BEGINNING, EDITOR_UP, EDITOR_BEGINNING_DOC -> { + focusedTextField.home(); + event.consume(); + } + case EDITOR_END, EDITOR_DOWN, EDITOR_END_DOC -> { + focusedTextField.end(); + event.consume(); + } + case EDITOR_CAPITALIZE -> { + int pos = focusedTextField.getCaretPosition(); + String text = + focusedTextField.getText( + 0, focusedTextField.getText().length()); + ResultingStringState res = + StringManipulator.capitalize(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_LOWERCASE -> { + int pos = focusedTextField.getCaretPosition(); + String text = + focusedTextField.getText( + 0, focusedTextField.getText().length()); + ResultingStringState res = + StringManipulator.lowercase(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_UPPERCASE -> { + int pos = focusedTextField.getCaretPosition(); + String text = + focusedTextField.getText( + 0, focusedTextField.getText().length()); + ResultingStringState res = + StringManipulator.uppercase(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_LINE -> { + int pos = focusedTextField.getCaretPosition(); + focusedTextField.setText(focusedTextField.getText(0, pos)); + focusedTextField.positionCaret(pos); + event.consume(); + } + case EDITOR_KILL_WORD -> { + int pos = focusedTextField.getCaretPosition(); + String text = + focusedTextField.getText( + 0, focusedTextField.getText().length()); + ResultingStringState res = + StringManipulator.killWord(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + case EDITOR_KILL_WORD_BACKWARD -> { + int pos = focusedTextField.getCaretPosition(); + String text = + focusedTextField.getText( + 0, focusedTextField.getText().length()); + ResultingStringState res = + StringManipulator.backwardKillWord(pos, text); + focusedTextField.setText(res.text); + focusedTextField.positionCaret(res.caretPosition); + event.consume(); + } + // CLEAR_SEARCH is handled at + // org.jabref.gui.search.SearchTextField + } + }); } } } diff --git a/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java b/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java index f967733b32c2..4755393d899d 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/AbstractPropertiesTabView.java @@ -1,14 +1,15 @@ package org.jabref.gui.libraryproperties; +import jakarta.inject.Inject; + import javafx.scene.Node; import javafx.scene.layout.VBox; import org.jabref.gui.DialogService; import org.jabref.model.database.BibDatabaseContext; -import jakarta.inject.Inject; - -public abstract class AbstractPropertiesTabView extends VBox implements PropertiesTab { +public abstract class AbstractPropertiesTabView extends VBox + implements PropertiesTab { @Inject protected DialogService dialogService; diff --git a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java index 1632c696d433..8e83b4cd175c 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java +++ b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesAction.java @@ -1,17 +1,17 @@ package org.jabref.gui.libraryproperties; -import java.util.function.Supplier; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + +import com.airhacks.afterburner.injection.Injector; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.model.database.BibDatabaseContext; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.util.function.Supplier; public class LibraryPropertiesAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(LibraryPropertiesAction.class); @@ -24,7 +24,8 @@ public LibraryPropertiesAction(StateManager stateManager) { this.executable.bind(needsDatabase(stateManager)); } - public LibraryPropertiesAction(Supplier databaseContext, StateManager stateManager) { + public LibraryPropertiesAction( + Supplier databaseContext, StateManager stateManager) { this.stateManager = stateManager; this.alternateDatabase = databaseContext; } @@ -34,10 +35,12 @@ public void execute() { DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); if (alternateDatabase != null) { - dialogService.showCustomDialogAndWait(new LibraryPropertiesView(alternateDatabase.get())); + dialogService.showCustomDialogAndWait( + new LibraryPropertiesView(alternateDatabase.get())); } else { if (stateManager.getActiveDatabase().isPresent()) { - dialogService.showCustomDialogAndWait(new LibraryPropertiesView(stateManager.getActiveDatabase().get())); + dialogService.showCustomDialogAndWait( + new LibraryPropertiesView(stateManager.getActiveDatabase().get())); } else { LOGGER.warn("No library selected."); } diff --git a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java index 5286d4fbfbf6..606deede3587 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesView.java @@ -1,5 +1,9 @@ package org.jabref.gui.libraryproperties; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.fxml.FXML; import javafx.scene.control.ButtonType; import javafx.scene.control.ScrollPane; @@ -12,9 +16,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - public class LibraryPropertiesView extends BaseDialog { @FXML private TabPane tabPane; @@ -28,14 +29,16 @@ public class LibraryPropertiesView extends BaseDialog savePreferencesAndCloseDialog()); + ControlHelper.setAction( + saveButton, getDialogPane(), event -> savePreferencesAndCloseDialog()); if (databaseContext.getDatabasePath().isPresent()) { - setTitle(Localization.lang("%0 - Library properties", databaseContext.getDatabasePath().get().getFileName())); + setTitle( + Localization.lang( + "%0 - Library properties", + databaseContext.getDatabasePath().get().getFileName())); } else { setTitle(Localization.lang("Library properties")); } diff --git a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java index 9939528cc362..cce6379c3537 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java +++ b/src/main/java/org/jabref/gui/libraryproperties/LibraryPropertiesViewModel.java @@ -1,7 +1,5 @@ package org.jabref.gui.libraryproperties; -import java.util.List; - import org.jabref.gui.libraryproperties.constants.ConstantsPropertiesView; import org.jabref.gui.libraryproperties.contentselectors.ContentSelectorView; import org.jabref.gui.libraryproperties.general.GeneralPropertiesView; @@ -10,19 +8,21 @@ import org.jabref.gui.libraryproperties.saving.SavingPropertiesView; import org.jabref.model.database.BibDatabaseContext; +import java.util.List; + 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() { diff --git a/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java b/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java index fb262a59e802..6af1283f5119 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java +++ b/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsItemModel.java @@ -1,6 +1,10 @@ package org.jabref.gui.libraryproperties.constants; -import java.util.regex.Pattern; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -8,15 +12,11 @@ import org.jabref.logic.l10n.Localization; -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.regex.Pattern; public class ConstantsItemModel { - private final static Pattern IS_NUMBER = Pattern.compile("-?\\d+(\\.\\d+)?"); + private static final Pattern IS_NUMBER = Pattern.compile("-?\\d+(\\.\\d+)?"); private final StringProperty labelProperty = new SimpleStringProperty(); private final StringProperty contentProperty = new SimpleStringProperty(); @@ -29,8 +29,11 @@ public ConstantsItemModel(String label, String content) { this.labelProperty.setValue(label); this.contentProperty.setValue(content); - labelValidator = new FunctionBasedValidator<>(this.labelProperty, ConstantsItemModel::validateLabel); - contentValidator = new FunctionBasedValidator<>(this.contentProperty, ConstantsItemModel::validateContent); + labelValidator = + new FunctionBasedValidator<>(this.labelProperty, ConstantsItemModel::validateLabel); + contentValidator = + new FunctionBasedValidator<>( + this.contentProperty, ConstantsItemModel::validateContent); combinedValidator = new CompositeValidator(labelValidator, contentValidator); } @@ -68,11 +71,14 @@ private static ValidationMessage validateLabel(String input) { } else if (input.trim().isEmpty()) { return ValidationMessage.error(Localization.lang("Please enter the string's label")); } else if (IS_NUMBER.matcher(input).matches()) { - return ValidationMessage.error(Localization.lang("The label of the string cannot be a number.")); + 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.")); + 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.")); + return ValidationMessage.error( + Localization.lang("The label of the string cannot contain spaces.")); } else { return null; // everything is ok } diff --git a/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java b/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java index f9e1008f0f2d..2de9bc2c3e45 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesView.java @@ -1,6 +1,8 @@ package org.jabref.gui.libraryproperties.constants; -import java.util.Optional; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -19,10 +21,10 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.util.Optional; -public class ConstantsPropertiesView extends AbstractPropertiesTabView implements PropertiesTab { +public class ConstantsPropertiesView extends AbstractPropertiesTabView + implements PropertiesTab { @FXML private TableView stringsList; @FXML private TableColumn labelColumn; @@ -36,9 +38,7 @@ public class ConstantsPropertiesView extends AbstractPropertiesTabView() .withValidation(ConstantsItemModel::labelValidation) .install(labelColumn, new DefaultStringConverter()); - labelColumn.setOnEditCommit((TableColumn.CellEditEvent cellEvent) -> { - - var tableView = cellEvent.getTableView(); - 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())); - cellItem.setLabel(cellEvent.getOldValue()); - } else { - cellItem.setLabel(cellEvent.getNewValue()); - } - - // Resort the entries based on the keys and set the focus to the newly-created entry - viewModel.resortStrings(); - var selectionModel = tableView.getSelectionModel(); - selectionModel.select(cellItem); - selectionModel.focus(selectionModel.getSelectedIndex()); - tableView.refresh(); - tableView.scrollTo(cellItem); - }); + labelColumn.setOnEditCommit( + (TableColumn.CellEditEvent cellEvent) -> { + var tableView = cellEvent.getTableView(); + 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())); + cellItem.setLabel(cellEvent.getOldValue()); + } else { + cellItem.setLabel(cellEvent.getNewValue()); + } + + // Resort the entries based on the keys and set the focus to the newly-created + // entry + viewModel.resortStrings(); + var selectionModel = tableView.getSelectionModel(); + selectionModel.select(cellItem); + selectionModel.focus(selectionModel.getSelectedIndex()); + tableView.refresh(); + tableView.scrollTo(cellItem); + }); contentColumn.setSortable(true); contentColumn.setReorderable(false); @@ -90,8 +97,9 @@ public void initialize() { new ViewModelTextFieldTableCellVisualizationFactory() .withValidation(ConstantsItemModel::contentValidation) .install(contentColumn, new DefaultStringConverter()); - contentColumn.setOnEditCommit((TableColumn.CellEditEvent cell) -> - cell.getRowValue().setContent(cell.getNewValue())); + contentColumn.setOnEditCommit( + (TableColumn.CellEditEvent cell) -> + cell.getRowValue().setContent(cell.getNewValue())); actionsColumn.setSortable(false); actionsColumn.setReorderable(false); @@ -99,8 +107,11 @@ public void initialize() { new ValueTableCellFactory() .withGraphic(label -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) .withTooltip(label -> Localization.lang("Remove string %0", label)) - .withOnMouseClickedEvent(item -> evt -> - viewModel.removeString(stringsList.getFocusModel().getFocusedItem())) + .withOnMouseClickedEvent( + item -> + evt -> + viewModel.removeString( + stringsList.getFocusModel().getFocusedItem())) .install(actionsColumn); stringsList.itemsProperty().bindBidirectional(viewModel.stringsListProperty()); diff --git a/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java b/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java index 3d84596e872e..213634a600cb 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java +++ b/src/main/java/org/jabref/gui/libraryproperties/constants/ConstantsPropertiesViewModel.java @@ -1,9 +1,6 @@ package org.jabref.gui.libraryproperties.constants; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -22,7 +19,10 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibtexString; -import com.tobiasdiez.easybind.EasyBind; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Optional; public class ConstantsPropertiesViewModel implements PropertiesTabViewModel { @@ -38,22 +38,28 @@ public class ConstantsPropertiesViewModel implements PropertiesTabViewModel { 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); - validProperty.bind(EasyBind.combine(allValidProperty, stream -> stream.allMatch(valid -> valid))); + 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() { @@ -77,7 +83,8 @@ public void removeString(ConstantsItemModel item) { public void resortStrings() { // Resort the strings list in the same order as setValues() does - stringsListProperty.sort(Comparator.comparing(c -> c.labelProperty().get().toLowerCase(Locale.ROOT))); + stringsListProperty.sort( + Comparator.comparing(c -> c.labelProperty().get().toLowerCase(Locale.ROOT))); } private ConstantsItemModel convertFromBibTexString(BibtexString bibtexString) { @@ -86,9 +93,8 @@ 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); } @@ -100,12 +106,13 @@ private BibtexString fromBibtexStringViewModel(ConstantsItemModel viewModel) { public Optional labelAlreadyExists(String label) { return stringsListProperty.stream() - .filter(item -> item.labelProperty().getValue().equals(label)) - .findFirst(); + .filter(item -> item.labelProperty().getValue().equals(label)) + .findFirst(); } public void openHelpPage() { - new HelpAction(HelpFile.STRING_EDITOR, dialogService, externalApplicationsPreferences).execute(); + new HelpAction(HelpFile.STRING_EDITOR, dialogService, externalApplicationsPreferences) + .execute(); } public ListProperty stringsListProperty() { diff --git a/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java b/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java index 90f354b71ce3..628539b4c701 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorView.java @@ -1,7 +1,9 @@ package org.jabref.gui.libraryproperties.contentselectors; -import java.util.Optional; -import java.util.function.Supplier; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; import javafx.beans.property.ListProperty; import javafx.fxml.FXML; @@ -16,9 +18,8 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; +import java.util.Optional; +import java.util.function.Supplier; public class ContentSelectorView extends AbstractPropertiesTabView { @@ -35,9 +36,7 @@ public class ContentSelectorView extends AbstractPropertiesTabView() .withText(Field::getDisplayName) .install(fieldsListView); @@ -65,7 +66,9 @@ private void initFieldNameComponents() { private void initKeywordsComponents() { initListView(keywordsListView, viewModel::getKeywordsBackingList); - viewModel.selectedKeywordProperty().bind(keywordsListView.getSelectionModel().selectedItemProperty()); + viewModel + .selectedKeywordProperty() + .bind(keywordsListView.getSelectionModel().selectedItemProperty()); addKeywordButton.disableProperty().bind(viewModel.isFieldNameListEmpty()); removeKeywordButton.disableProperty().bind(viewModel.isNoKeywordSelected()); } @@ -94,7 +97,8 @@ private void removeKeyword() { } } - private void initListView(ListView listViewToInit, Supplier> backingList) { + private void initListView( + ListView listViewToInit, Supplier> backingList) { listViewToInit.itemsProperty().bind(backingList.get()); listViewToInit.getSelectionModel().selectFirst(); } @@ -104,6 +108,7 @@ private Optional getSelectedField() { } private Optional getSelectedKeyword() { - return Optional.of(keywordsListView.getSelectionModel()).map(SelectionModel::getSelectedItem); + return Optional.of(keywordsListView.getSelectionModel()) + .map(SelectionModel::getSelectedItem); } } diff --git a/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java b/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java index 2f59c1490cb7..abde8b8b1548 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java +++ b/src/main/java/org/jabref/gui/libraryproperties/contentselectors/ContentSelectorViewModel.java @@ -1,13 +1,5 @@ package org.jabref.gui.libraryproperties.contentselectors; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ListProperty; @@ -29,6 +21,14 @@ import org.jabref.model.metadata.ContentSelectors; import org.jabref.model.metadata.MetaData; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class ContentSelectorViewModel implements PropertiesTabViewModel { private final MetaData metaData; @@ -38,8 +38,10 @@ public class ContentSelectorViewModel implements PropertiesTabViewModel { // The map from each field to its predefined strings ("keywords") that can be selected private Map> fieldKeywordsMap = new HashMap<>(); - private final ListProperty fields = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty keywords = new SimpleListProperty<>(FXCollections.observableArrayList()); + 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(); @@ -51,7 +53,9 @@ public class ContentSelectorViewModel implements PropertiesTabViewModel { @Override public void setValues() { // Populate field names keyword map - fieldKeywordsMap = ContentSelectors.getFieldKeywordsMap(metaData.getContentSelectors().getContentSelectors()); + fieldKeywordsMap = + ContentSelectors.getFieldKeywordsMap( + metaData.getContentSelectors().getContentSelectors()); // Populate Field names list List existingFields = new ArrayList<>(fieldKeywordsMap.keySet()); @@ -69,12 +73,17 @@ public void storeSettings() { if (ContentSelectors.isDefaultMap(fieldKeywordsMap)) { // Remove all fields of the content selector - fieldNamesToRemove = metaData.getContentSelectorsSorted().stream().map(ContentSelector::getField).toList(); + fieldNamesToRemove = + metaData.getContentSelectorsSorted().stream() + .map(ContentSelector::getField) + .toList(); } else { fieldNamesToRemove = determineFieldsToRemove(); } - fieldKeywordsMap.forEach((field, keywords) -> updateMetaDataContentSelector(metaDataFields, field, keywords)); + fieldKeywordsMap.forEach( + (field, keywords) -> + updateMetaDataContentSelector(metaDataFields, field, keywords)); fieldNamesToRemove.forEach(metaData::clearContentSelectors); } @@ -107,18 +116,23 @@ 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 +146,12 @@ 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); @@ -155,14 +171,17 @@ void populateKeywords(Field selectedField) { } void showInputKeywordDialog(Field selectedField) { - dialogService.showInputDialogAndWait(Localization.lang("Add new keyword"), Localization.lang("Keyword:")) - .ifPresent(newKeyword -> addKeywordIfUnique(selectedField, newKeyword)); + dialogService + .showInputDialogAndWait( + Localization.lang("Add new keyword"), Localization.lang("Keyword:")) + .ifPresent(newKeyword -> addKeywordIfUnique(selectedField, newKeyword)); } private void addKeywordIfUnique(Field field, String keywordToAdd) { boolean exists = fieldKeywordsMap.get(field).contains(keywordToAdd); if (exists) { - dialogService.showErrorDialogAndWait(Localization.lang("Keyword \"%0\" already exists", keywordToAdd)); + dialogService.showErrorDialogAndWait( + Localization.lang("Keyword \"%0\" already exists", keywordToAdd)); return; } @@ -174,7 +193,12 @@ 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); } @@ -197,19 +221,27 @@ 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()); + result.addAll( + fieldKeywordsMap.entrySet().stream() + .filter( + entry -> + ContentSelectors.DEFAULT_FIELD_NAMES.contains( + entry.getKey()) + && entry.getValue().isEmpty()) + .map(Map.Entry::getKey) + .toList()); return result; } - private void updateMetaDataContentSelector(List existingFields, Field field, List keywords) { + private void updateMetaDataContentSelector( + List existingFields, Field field, List keywords) { boolean fieldNameDoNotExists = !existingFields.contains(field); if (fieldNameDoNotExists) { metaData.addContentSelector(new ContentSelector(field, keywords)); diff --git a/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java b/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java index 0999708a5743..199fc1e38b98 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java @@ -1,6 +1,8 @@ package org.jabref.gui.libraryproperties.general; -import java.nio.charset.Charset; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; @@ -13,8 +15,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import java.nio.charset.Charset; public class GeneralPropertiesView extends AbstractPropertiesTabView { @FXML private ComboBox encoding; @@ -28,9 +29,7 @@ public class GeneralPropertiesView extends AbstractPropertiesTabView() - .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()); @@ -54,8 +52,12 @@ public void initialize() { databaseMode.itemsProperty().bind(viewModel.databaseModesProperty()); databaseMode.valueProperty().bindBidirectional(viewModel.selectedDatabaseModeProperty()); - generalFileDirectory.textProperty().bindBidirectional(viewModel.generalFileDirectoryPropertyProperty()); - userSpecificFileDirectory.textProperty().bindBidirectional(viewModel.userSpecificFileDirectoryProperty()); + generalFileDirectory + .textProperty() + .bindBidirectional(viewModel.generalFileDirectoryPropertyProperty()); + userSpecificFileDirectory + .textProperty() + .bindBidirectional(viewModel.userSpecificFileDirectoryProperty()); laTexFileDirectory.textProperty().bindBidirectional(viewModel.laTexFileDirectoryProperty()); } diff --git a/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java b/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java index b42af859eadc..78adf076bce5 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java +++ b/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.libraryproperties.general; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -24,13 +20,21 @@ import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.metadata.MetaData; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + public class GeneralPropertiesViewModel implements PropertiesTabViewModel { private final BooleanProperty encodingDisableProperty = new SimpleBooleanProperty(); - private final ListProperty encodingsProperty = new SimpleListProperty<>(FXCollections.observableArrayList(Encodings.getCharsets())); - private final ObjectProperty selectedEncodingProperty = new SimpleObjectProperty<>(Encodings.getCharsets().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(Encodings.getCharsets())); + private final ObjectProperty selectedEncodingProperty = + new SimpleObjectProperty<>(Encodings.getCharsets().getFirst()); + private final ListProperty databaseModesProperty = + new SimpleListProperty<>(FXCollections.observableArrayList(BibDatabaseMode.values())); + private final SimpleObjectProperty selectedDatabaseModeProperty = + new SimpleObjectProperty<>(BibDatabaseMode.BIBLATEX); private final StringProperty generalFileDirectoryProperty = new SimpleStringProperty(""); private final StringProperty userSpecificFileDirectoryProperty = new SimpleStringProperty(""); private final StringProperty laTexFileDirectoryProperty = new SimpleStringProperty(""); @@ -42,26 +46,39 @@ public class GeneralPropertiesViewModel implements PropertiesTabViewModel { private final MetaData metaData; private final DirectoryDialogConfiguration directoryDialogConfiguration; - 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(); - this.directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(preferences.getFilePreferences().getWorkingDirectory()).build(); + this.directoryDialogConfiguration = + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory( + preferences.getFilePreferences().getWorkingDirectory()) + .build(); } @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)); generalFileDirectoryProperty.setValue(metaData.getDefaultFileDirectory().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 @@ -82,32 +99,46 @@ public void storeSettings() { if (userSpecificFileDirectory.isEmpty()) { newMetaData.clearUserFileDirectory(preferences.getFilePreferences().getUserAndHost()); } else { - newMetaData.setUserFileDirectory(preferences.getFilePreferences().getUserAndHost(), userSpecificFileDirectory); + newMetaData.setUserFileDirectory( + preferences.getFilePreferences().getUserAndHost(), userSpecificFileDirectory); } String latexFileDirectory = laTexFileDirectoryProperty.getValue(); if (latexFileDirectory.isEmpty()) { newMetaData.clearLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()); } else { - newMetaData.setLatexFileDirectory(preferences.getFilePreferences().getUserAndHost(), Path.of(latexFileDirectory)); + newMetaData.setLatexFileDirectory( + preferences.getFilePreferences().getUserAndHost(), Path.of(latexFileDirectory)); } databaseContext.setMetaData(newMetaData); } public void browseGeneralDir() { - dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(dir -> generalFileDirectoryProperty.setValue(dir.toAbsolutePath().toString())); + dialogService + .showDirectorySelectionDialog(directoryDialogConfiguration) + .ifPresent( + dir -> + generalFileDirectoryProperty.setValue( + dir.toAbsolutePath().toString())); } public void browseUserDir() { - dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(dir -> userSpecificFileDirectoryProperty.setValue(dir.toAbsolutePath().toString())); + dialogService + .showDirectorySelectionDialog(directoryDialogConfiguration) + .ifPresent( + dir -> + userSpecificFileDirectoryProperty.setValue( + dir.toAbsolutePath().toString())); } public void browseLatexDir() { - dialogService.showDirectorySelectionDialog(directoryDialogConfiguration) - .ifPresent(dir -> laTexFileDirectoryProperty.setValue(dir.toAbsolutePath().toString())); + dialogService + .showDirectorySelectionDialog(directoryDialogConfiguration) + .ifPresent( + dir -> + laTexFileDirectoryProperty.setValue( + dir.toAbsolutePath().toString())); } public BooleanProperty encodingDisableProperty() { diff --git a/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java b/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java index e391efb5370b..1c3addc9d9c3 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/keypattern/KeyPatternPropertiesView.java @@ -1,5 +1,9 @@ package org.jabref.gui.libraryproperties.keypattern; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -15,10 +19,8 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; -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; @@ -29,9 +31,7 @@ public class KeyPatternPropertiesView extends AbstractPropertiesTabView patternListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty defaultKeyPatternProperty = new SimpleObjectProperty<>( - new CitationKeyPatternsPanelItemModel(new CitationKeyPatternsPanelViewModel.DefaultEntryType(), "")); + // 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 CliPreferences preferences; private final BibDatabaseContext databaseContext; - public KeyPatternPropertiesViewModel(BibDatabaseContext databaseContext, CliPreferences preferences) { + public KeyPatternPropertiesViewModel( + BibDatabaseContext databaseContext, CliPreferences preferences) { this.databaseContext = databaseContext; this.preferences = preferences; } @@ -37,19 +43,23 @@ 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(); - if (!"default".equals(item.getEntryType().getName())) { - if (!patternString.trim().isEmpty()) { - newKeyPattern.addCitationKeyPattern(item.getEntryType(), patternString); - } - } - }); + patternListProperty.forEach( + item -> { + String patternString = item.getPattern(); + if (!"default".equals(item.getEntryType().getName())) { + if (!patternString.trim().isEmpty()) { + newKeyPattern.addCitationKeyPattern(item.getEntryType(), patternString); + } + } + }); 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()); } diff --git a/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java b/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java index 36a5f106fe05..566c65eb67dd 100644 --- a/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java +++ b/src/main/java/org/jabref/gui/libraryproperties/preamble/PreamblePropertiesView.java @@ -1,6 +1,8 @@ package org.jabref.gui.libraryproperties.preamble; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.TextArea; @@ -9,8 +11,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; +import javax.swing.undo.UndoManager; public class PreamblePropertiesView extends AbstractPropertiesTabView { @FXML private TextArea preamble; @@ -20,9 +21,7 @@ public class PreamblePropertiesView extends AbstractPropertiesTabView implements PropertiesTab { +public class SavingPropertiesView extends AbstractPropertiesTabView + implements PropertiesTab { @FXML private CheckBox protect; @FXML private SaveOrderConfigPanel saveOrderConfigPanel; @@ -25,9 +27,7 @@ public class SavingPropertiesView extends AbstractPropertiesTabView 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) { + public SavingPropertiesViewModel( + BibDatabaseContext databaseContext, CliPreferences preferences) { this.databaseContext = databaseContext; this.preferences = preferences; this.initialMetaData = databaseContext.getMetaData(); @@ -83,21 +90,28 @@ 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 Optional saveActions = initialMetaData.getSaveActions(); - saveActions.ifPresentOrElse(value -> { - cleanupsDisableProperty.setValue(!value.isEnabled()); - cleanupsProperty.setValue(FXCollections.observableArrayList(value.getConfiguredActions())); - }, () -> { - CleanupPreferences defaultPreset = preferences.getDefaultCleanupPreset(); - cleanupsDisableProperty.setValue(!defaultPreset.getFieldFormatterCleanups().isEnabled()); - cleanupsProperty.setValue(FXCollections.observableArrayList(defaultPreset.getFieldFormatterCleanups().getConfiguredActions())); - }); + saveActions.ifPresentOrElse( + value -> { + cleanupsDisableProperty.setValue(!value.isEnabled()); + cleanupsProperty.setValue( + FXCollections.observableArrayList(value.getConfiguredActions())); + }, + () -> { + CleanupPreferences defaultPreset = preferences.getDefaultCleanupPreset(); + cleanupsDisableProperty.setValue( + !defaultPreset.getFieldFormatterCleanups().isEnabled()); + cleanupsProperty.setValue( + FXCollections.observableArrayList( + defaultPreset + .getFieldFormatterCleanups() + .getConfiguredActions())); + }); } @Override @@ -110,11 +124,12 @@ public void storeSettings() { newMetaData.markAsNotProtected(); } - FieldFormatterCleanups fieldFormatterCleanups = new FieldFormatterCleanups( - !cleanupsDisableProperty().getValue(), - cleanupsProperty()); + FieldFormatterCleanups fieldFormatterCleanups = + new FieldFormatterCleanups( + !cleanupsDisableProperty().getValue(), cleanupsProperty()); - if (FieldFormatterCleanups.DEFAULT_SAVE_ACTIONS.equals(fieldFormatterCleanups.getConfiguredActions())) { + if (FieldFormatterCleanups.DEFAULT_SAVE_ACTIONS.equals( + fieldFormatterCleanups.getConfiguredActions())) { newMetaData.clearSaveActions(); } else { // if all actions have been removed, remove the save actions from the MetaData @@ -125,9 +140,14 @@ public void storeSettings() { } } - SaveOrder newSaveOrder = new SaveOrder( - SaveOrder.OrderType.fromBooleans(saveInSpecifiedOrderProperty.getValue(), saveInOriginalProperty.getValue()), - sortCriteriaProperty.stream().map(SortCriterionViewModel::getCriterion).toList()); + SaveOrder newSaveOrder = + new SaveOrder( + SaveOrder.OrderType.fromBooleans( + saveInSpecifiedOrderProperty.getValue(), + saveInOriginalProperty.getValue()), + sortCriteriaProperty.stream() + .map(SortCriterionViewModel::getCriterion) + .toList()); if (!newSaveOrder.equals(saveOrder)) { if (newSaveOrder.equals(SaveOrder.getDefaultSaveOrder())) { diff --git a/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java b/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java index e9036bceffb8..4a5e20c8e48e 100644 --- a/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/AttachFileAction.java @@ -1,8 +1,5 @@ package org.jabref.gui.linkedfile; -import java.nio.file.Path; -import java.util.Optional; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -19,6 +16,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; +import java.nio.file.Path; +import java.util.Optional; + public class AttachFileAction extends SimpleCommand { private final LibraryTab libraryTab; @@ -27,11 +27,12 @@ public class AttachFileAction extends SimpleCommand { 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; @@ -49,7 +50,8 @@ public void execute() { } if (stateManager.getSelectedEntries().size() != 1) { - dialogService.notify(Localization.lang("This operation requires exactly one item to be selected.")); + dialogService.notify( + Localization.lang("This operation requires exactly one item to be selected.")); return; } @@ -57,30 +59,42 @@ public void execute() { BibEntry entry = stateManager.getSelectedEntries().getFirst(); - Path workingDirectory = databaseContext.getFirstExistingFileDir(filePreferences) - .orElse(filePreferences.getWorkingDirectory()); + Path workingDirectory = + databaseContext + .getFirstExistingFileDir(filePreferences) + .orElse(filePreferences.getWorkingDirectory()); - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(workingDirectory) - .build(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .withInitialDirectory(workingDirectory) + .build(); - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(newFile -> { - LinkedFile linkedFile = LinkedFilesEditorViewModel.fromFile( - newFile, - databaseContext.getFileDirectories(filePreferences), - externalApplicationsPreferences); + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + newFile -> { + LinkedFile linkedFile = + LinkedFilesEditorViewModel.fromFile( + newFile, + databaseContext.getFileDirectories(filePreferences), + externalApplicationsPreferences); - LinkedFileEditDialog dialog = new LinkedFileEditDialog(linkedFile); + 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/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java b/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java index 61ec0f65ed45..dc38d6c2b710 100644 --- a/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java @@ -1,10 +1,5 @@ package org.jabref.gui.linkedfile; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.util.Optional; - import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -19,6 +14,11 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.StandardField; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Optional; + public class AttachFileFromURLAction extends SimpleCommand { private final StateManager stateManager; @@ -26,10 +26,11 @@ public class AttachFileFromURLAction extends SimpleCommand { 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; @@ -46,7 +47,8 @@ public void execute() { } if (stateManager.getSelectedEntries().size() != 1) { - dialogService.notify(Localization.lang("This operation requires exactly one item to be selected.")); + dialogService.notify( + Localization.lang("This operation requires exactly one item to be selected.")); return; } @@ -54,7 +56,8 @@ public void execute() { BibEntry entry = stateManager.getSelectedEntries().getFirst(); - Optional urlforDownload = getUrlForDownloadFromClipBoardOrEntry(dialogService, entry); + Optional urlforDownload = + getUrlForDownloadFromClipBoardOrEntry(dialogService, entry); if (urlforDownload.isEmpty()) { return; @@ -62,32 +65,46 @@ public void execute() { try { URL url = URI.create(urlforDownload.get()).toURL(); - 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) { dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); } } - public static Optional getUrlForDownloadFromClipBoardOrEntry(DialogService dialogService, BibEntry entry) { + public static Optional getUrlForDownloadFromClipBoardOrEntry( + DialogService dialogService, BibEntry entry) { String clipText = ClipBoardManager.getContents(); 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); + 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.showInputDialogAndWait( + Localization.lang("Download file"), + Localization.lang("Enter URL to download")); } return urlText; } diff --git a/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java b/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java index 6925cbd88d5c..e058772791c9 100644 --- a/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java @@ -1,11 +1,5 @@ package org.jabref.gui.linkedfile; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; - import javafx.collections.FXCollections; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; @@ -26,12 +20,17 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.LinkedFile; - import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + @NullMarked public class DeleteFileAction extends SimpleCommand { @@ -44,11 +43,12 @@ public class DeleteFileAction extends SimpleCommand { 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 +59,11 @@ 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); } @@ -73,7 +74,8 @@ private boolean deletionOfCompleteEntry() { @Override public void execute() { if (filesToDelete.isEmpty()) { - dialogService.notify(Localization.lang("This operation requires selected linked files.")); + dialogService.notify( + Localization.lang("This operation requires selected linked files.")); return; } @@ -98,7 +100,8 @@ public void execute() { Path path = file.get(); dialogTitle = Localization.lang("Delete '%0'", path.getFileName().toString()); } else { - dialogService.notify(Localization.lang("Error accessing file '%0'.", linkedFile.getLink())); + dialogService.notify( + Localization.lang("Error accessing file '%0'.", linkedFile.getLink())); // Deleting a non-existing file is a success success = true; return; @@ -115,10 +118,16 @@ public void execute() { } ButtonType deleteFromDisk = new ButtonType(label); - ButtonType removeFromEntry = new ButtonType(Localization.lang("Keep file(s)"), ButtonBar.ButtonData.YES); + 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(); @@ -135,11 +144,13 @@ private DialogPane createDeleteFilesDialog(String description) { warning.setGlyphSize(24.0); Label header = new Label(description, warning); header.setWrapText(true); - header.setStyle(""" + header.setStyle( + """ -fx-padding: 10px; -fx-background-color: -fx-background;"""); - ListView filesToDeleteList = new ListView<>(FXCollections.observableArrayList(filesToDelete)); + ListView filesToDeleteList = + new ListView<>(FXCollections.observableArrayList(filesToDelete)); new ViewModelListCellFactory() .withText(item -> item.getFile().getLink()) .install(filesToDeleteList); @@ -179,7 +190,8 @@ private void deleteFileHelper(BibDatabaseContext databaseContext, LinkedFile lin if (file.isEmpty()) { LOGGER.warn("Could not find file {}", linkedFile.getLink()); - dialogService.notify(Localization.lang("Error accessing file '%0'.", linkedFile.getLink())); + dialogService.notify( + Localization.lang("Error accessing file '%0'.", linkedFile.getLink())); // Deleting a non-existing file is a success success = true; return; @@ -199,7 +211,9 @@ private void deleteFileHelper(BibDatabaseContext databaseContext, LinkedFile lin success = true; } 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); } } diff --git a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java index 47402708ad45..27df6fa035bd 100644 --- a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java @@ -1,21 +1,13 @@ package org.jabref.gui.linkedfile; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import javax.net.ssl.SSLHandshakeException; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.SimpleDoubleProperty; +import kong.unirest.core.UnirestException; + import org.jabref.gui.DialogService; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.externalfiletype.ExternalFileType; @@ -37,12 +29,21 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; - -import com.tobiasdiez.easybind.EasyBind; -import kong.unirest.core.UnirestException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javax.net.ssl.SSLHandshakeException; + public class DownloadLinkedFileAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(DownloadLinkedFileAction.class); @@ -62,16 +63,17 @@ public class DownloadLinkedFileAction extends SimpleCommand { 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; @@ -83,21 +85,24 @@ public DownloadLinkedFileAction(BibDatabaseContext databaseContext, this.taskExecutor = taskExecutor; this.keepHtmlLink = keepHtmlLink; - this.linkedFileHandler = new LinkedFileHandler(linkedFile, entry, databaseContext, filePreferences); + this.linkedFileHandler = + new LinkedFileHandler(linkedFile, entry, databaseContext, filePreferences); } /** * 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, + public DownloadLinkedFileAction( + BibDatabaseContext databaseContext, + BibEntry entry, + LinkedFile linkedFile, + String downloadUrl, + DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences, + FilePreferences filePreferences, + TaskExecutor taskExecutor) { + this( + databaseContext, entry, linkedFile, downloadUrl, @@ -113,12 +118,15 @@ public DownloadLinkedFileAction(BibDatabaseContext databaseContext, public void execute() { LOGGER.info("Downloading file from {}", downloadUrl); if (downloadUrl.isEmpty() || !LinkedFile.isOnlineLink(downloadUrl)) { - throw new UnsupportedOperationException("In order to download the file, the url has to be an online link"); + throw new UnsupportedOperationException( + "In order to download the file, the url has to be an online link"); } 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; } @@ -139,9 +147,16 @@ 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,7 +174,9 @@ private void onSuccess(Path targetDirectory, Path downloadedFile) { boolean isDuplicate; boolean isHtml; try { - isDuplicate = FileNameUniqueness.isDuplicatedFile(targetDirectory, downloadedFile.getFileName(), dialogService::notify); + isDuplicate = + FileNameUniqueness.isDuplicatedFile( + targetDirectory, downloadedFile.getFileName(), dialogService::notify); } catch (IOException e) { LOGGER.error("FileNameUniqueness.isDuplicatedFile failed", e); return; @@ -167,20 +184,28 @@ private void onSuccess(Path targetDirectory, Path downloadedFile) { 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()) { newLinkedFile.setSourceURL(linkedFile.getSourceUrl()); @@ -189,9 +214,12 @@ private void onSuccess(Path targetDirectory, Path downloadedFile) { isHtml = newLinkedFile.getFileType().equals(StandardExternalFileType.URL.getName()); if (isHtml) { if (this.keepHtmlLink) { - dialogService.notify(Localization.lang("Download '%0' was a HTML file. Keeping URL.", downloadUrl)); + dialogService.notify( + Localization.lang( + "Download '%0' was a HTML file. Keeping URL.", downloadUrl)); } else { - dialogService.notify(Localization.lang("Download '%0' was a HTML file. Removed.", downloadUrl)); + dialogService.notify( + Localization.lang("Download '%0' was a HTML file. Removed.", downloadUrl)); List newFiles = new ArrayList<>(entry.getFiles()); newFiles.remove(linkedFile); entry.setFiles(newFiles); @@ -213,7 +241,11 @@ private void onFailure(URLDownload urlDownload, Exception ex) { } 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)); } } @@ -222,8 +254,10 @@ private boolean checkSSLHandshake(URLDownload urlDownload) { urlDownload.canBeReached(); } 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?", + if (dialogService.showConfirmationDialogAndWait( + Localization.lang("Download file"), + Localization.lang( + "Unable to find valid certification path to requested target(%0), download anyway?", urlDownload.getSource().toString()))) { return true; } else { @@ -239,28 +273,40 @@ private boolean checkSSLHandshake(URLDownload urlDownload) { return true; } - 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); - }) + 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); - }); + .onFinished( + () -> { + downloadProgress.unbind(); + downloadProgress.set(1); + }); } private Optional inferFileType(URLDownload urlDownload) { @@ -274,16 +320,22 @@ 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() { @@ -305,7 +357,9 @@ public Path call() throws Exception { try (ProgressInputStream inputStream = download.asInputStream()) { EasyBind.subscribe( inputStream.totalNumBytesReadProperty(), - bytesRead -> updateProgress(bytesRead.longValue(), inputStream.getMaxNumBytes())); + bytesRead -> + updateProgress( + bytesRead.longValue(), inputStream.getMaxNumBytes())); // Make sure directory exists since otherwise copy fails Files.createDirectories(destination.getParent()); Files.copy(inputStream, destination, StandardCopyOption.REPLACE_EXISTING); diff --git a/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java b/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java index f6290544bc73..9d7cb0334754 100644 --- a/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java +++ b/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialog.java @@ -1,5 +1,9 @@ package org.jabref.gui.linkedfile; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.ButtonType; @@ -16,12 +20,10 @@ import org.jabref.logic.util.StandardFileType; import org.jabref.model.entry.LinkedFile; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - 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; @@ -55,26 +57,31 @@ 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); this.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, primaryButtonType); - this.setResultConverter(button -> { - if (button == primaryButtonType) { - return viewModel.getNewLinkedFile(); - } else { - return null; - } - }); + this.setResultConverter( + button -> { + if (button == primaryButtonType) { + return viewModel.getNewLinkedFile(); + } else { + return null; + } + }); } @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() diff --git a/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java b/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java index cd31283742c2..a27cc46143f2 100644 --- a/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java +++ b/src/main/java/org/jabref/gui/linkedfile/LinkedFileEditDialogViewModel.java @@ -1,10 +1,7 @@ package org.jabref.gui.linkedfile; -import java.net.MalformedURLException; -import java.net.URI; -import java.nio.file.Path; -import java.util.Optional; -import java.util.regex.Pattern; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.optional.ObservableOptionalValue; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -26,8 +23,11 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.LinkedFile; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.optional.ObservableOptionalValue; +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.file.Path; +import java.util.Optional; +import java.util.regex.Pattern; public class LinkedFileEditDialogViewModel extends AbstractViewModel { @@ -35,24 +35,29 @@ public class LinkedFileEditDialogViewModel extends AbstractViewModel { 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 ObjectProperty selectedExternalFileType = new SimpleObjectProperty<>(); + 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); @@ -63,13 +68,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); } } @@ -81,18 +86,22 @@ public void openBrowseDialog() { Path workingDir = file.orElse(filePreferences.getWorkingDirectory()); String fileName = Path.of(fileText).getFileName().toString(); - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(workingDir) - .withInitialFileName(fileName) - .build(); - - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(path -> { - // Store the directory for next time: - filePreferences.setWorkingDirectory(path); - link.set(relativize(path)); - - setExternalFileTypeByExtension(link.getValueSafe()); - }); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .withInitialDirectory(workingDir) + .withInitialFileName(fileName) + .build(); + + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + path -> { + // Store the directory for next time: + filePreferences.setWorkingDirectory(path); + link.set(relativize(path)); + + setExternalFileTypeByExtension(link.getValueSafe()); + }); } public void setValues(LinkedFile linkedFile) { @@ -106,7 +115,9 @@ public void setValues(LinkedFile linkedFile) { } // 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())) { @@ -135,16 +146,26 @@ public ObjectProperty selectedExternalFileTypeProperty() { } public LinkedFile getNewLinkedFile() { - String fileType = monadicSelectedExternalFileType.getValue().map(ExternalFileType::toString).orElse(""); + String fileType = + monadicSelectedExternalFileType + .getValue() + .map(ExternalFileType::toString) + .orElse(""); if (LinkedFile.isOnlineLink(link.getValue())) { try { - return new LinkedFile(description.getValue(), URI.create(link.getValue()).toURL(), fileType, sourceUrl.getValue()); + return new LinkedFile( + description.getValue(), + URI.create(link.getValue()).toURL(), + fileType, + sourceUrl.getValue()); } catch (MalformedURLException e) { - return new LinkedFile(description.getValue(), link.getValue(), fileType, sourceUrl.getValue()); + return new LinkedFile( + description.getValue(), link.getValue(), fileType, sourceUrl.getValue()); } } - return new LinkedFile(description.getValue(), Path.of(link.getValue()), fileType, sourceUrl.getValue()); + return new LinkedFile( + description.getValue(), Path.of(link.getValue()), fileType, sourceUrl.getValue()); } private String relativize(Path filePath) { diff --git a/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java b/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java index ac12f783c7e4..eaff2577dd14 100644 --- a/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java @@ -1,8 +1,6 @@ package org.jabref.gui.linkedfile; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -13,15 +11,17 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; public class RedownloadMissingFilesAction extends SimpleCommand { - private static final Logger LOGGER = LoggerFactory.getLogger(RedownloadMissingFilesAction.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(RedownloadMissingFilesAction.class); private final StateManager stateManager; private final DialogService dialogService; @@ -31,11 +31,12 @@ public class RedownloadMissingFilesAction extends SimpleCommand { 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,9 +50,10 @@ public RedownloadMissingFilesAction(StateManager stateManager, public void execute() { if (stateManager.getActiveDatabase().isPresent()) { databaseContext = stateManager.getActiveDatabase().get(); - boolean confirm = dialogService.showConfirmationDialogAndWait( - Localization.lang("Redownload missing files"), - Localization.lang("Redownload missing files for current library?")); + boolean confirm = + dialogService.showConfirmationDialogAndWait( + Localization.lang("Redownload missing files"), + Localization.lang("Redownload missing files for current library?")); if (!confirm) { return; } @@ -64,31 +66,45 @@ public void execute() { */ private void redownloadMissing(BibDatabaseContext databaseContext) { LOGGER.info("Redownloading missing files"); - databaseContext.getEntries().forEach(entry -> { - entry.getFiles().forEach(linkedFile -> { - if (linkedFile.isOnlineLink() || linkedFile.getSourceUrl().isEmpty()) { - return; - } + databaseContext + .getEntries() + .forEach( + entry -> { + entry.getFiles() + .forEach( + linkedFile -> { + if (linkedFile.isOnlineLink() + || linkedFile.getSourceUrl().isEmpty()) { + return; + } - Optional path = FileUtil.find(this.databaseContext, linkedFile.getLink(), filePreferences); - if (path.isPresent() && Files.exists(path.get())) { - return; - } - String fileName = Path.of(linkedFile.getLink()).getFileName().toString(); + Optional path = + FileUtil.find( + this.databaseContext, + linkedFile.getLink(), + filePreferences); + if (path.isPresent() && Files.exists(path.get())) { + return; + } + String fileName = + Path.of(linkedFile.getLink()) + .getFileName() + .toString(); - DownloadLinkedFileAction downloadAction = new DownloadLinkedFileAction( - this.databaseContext, - entry, - linkedFile, - linkedFile.getSourceUrl(), - dialogService, - externalApplicationsPreferences, - filePreferences, - taskExecutor, - fileName, - true); - downloadAction.execute(); - }); - }); + DownloadLinkedFileAction downloadAction = + new DownloadLinkedFileAction( + this.databaseContext, + entry, + linkedFile, + linkedFile.getSourceUrl(), + dialogService, + externalApplicationsPreferences, + filePreferences, + taskExecutor, + fileName, + true); + downloadAction.execute(); + }); + }); } } diff --git a/src/main/java/org/jabref/gui/logging/GuiWriter.java b/src/main/java/org/jabref/gui/logging/GuiWriter.java index 4dff37ad4f3b..3af81425699b 100644 --- a/src/main/java/org/jabref/gui/logging/GuiWriter.java +++ b/src/main/java/org/jabref/gui/logging/GuiWriter.java @@ -1,16 +1,15 @@ package org.jabref.gui.logging; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Map; - import org.jabref.logic.logging.LogMessages; - import org.tinylog.core.LogEntry; import org.tinylog.core.LogEntryValue; import org.tinylog.writers.AbstractFormatPatternWriter; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Map; + public class GuiWriter extends AbstractFormatPatternWriter { public GuiWriter(final Map properties) { @@ -32,10 +31,8 @@ public void write(LogEntry logEntry) throws Exception { } @Override - public void flush() throws Exception { - } + public void flush() throws Exception {} @Override - public void close() throws Exception { - } + public void close() throws Exception {} } diff --git a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java index aa8fdc38ec38..4439a3486438 100644 --- a/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java +++ b/src/main/java/org/jabref/gui/maintable/BibEntryTableViewModel.java @@ -1,14 +1,8 @@ package org.jabref.gui.maintable; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; +import com.tobiasdiez.easybind.optional.OptionalBinding; import javafx.beans.Observable; import javafx.beans.binding.Binding; @@ -37,15 +31,22 @@ import org.jabref.model.groups.AbstractGroup; import org.jabref.model.groups.GroupTreeNode; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.EasyBinding; -import com.tobiasdiez.easybind.optional.OptionalBinding; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; 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 Map> + specialFieldValues = new HashMap<>(); private final EasyBinding> linkedFiles; private final EasyBinding> linkedIdentifiers; private final Binding> matchedGroups; @@ -56,14 +57,21 @@ public class BibEntryTableViewModel { 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); + private final ObjectProperty matchCategory = + new SimpleObjectProperty<>(MatchCategory.MATCHING_SEARCH_AND_GROUPS); - public BibEntryTableViewModel(BibEntry entry, BibDatabaseContext bibDatabaseContext, ObservableValue fieldValueFormatter) { + public BibEntryTableViewModel( + BibEntry entry, + BibDatabaseContext bibDatabaseContext, + ObservableValue fieldValueFormatter) { this.entry = entry; this.bibDatabaseContext = bibDatabaseContext; this.fieldValueFormatter = fieldValueFormatter; - this.linkedFiles = getField(StandardField.FILE).mapOpt(FileFieldParser::parse).orElseOpt(Collections.emptyList()); + this.linkedFiles = + getField(StandardField.FILE) + .mapOpt(FileFieldParser::parse) + .orElseOpt(Collections.emptyList()); this.linkedIdentifiers = createLinkedIdentifiersBinding(entry); this.matchedGroups = createMatchedGroupsBinding(bibDatabaseContext, entry); } @@ -90,15 +98,28 @@ 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(), - (a, b) -> - database.getMetaData().getGroups().map(groupTreeNode -> - groupTreeNode.getMatchingGroups(entry).stream() - .map(GroupTreeNode::getGroup) - .filter(Predicate.not(Predicate.isEqual(groupTreeNode.getGroup()))) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()))); + private static Binding> createMatchedGroupsBinding( + BibDatabaseContext database, BibEntry entry) { + return new UiThreadBinding<>( + EasyBind.combine( + entry.getFieldBinding(StandardField.GROUPS), + database.getMetaData().groupsBinding(), + (a, b) -> + database.getMetaData() + .getGroups() + .map( + groupTreeNode -> + groupTreeNode + .getMatchingGroups(entry) + .stream() + .map(GroupTreeNode::getGroup) + .filter( + Predicate.not( + Predicate.isEqual( + groupTreeNode + .getGroup()))) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()))); } public OptionalBinding getField(Field field) { @@ -117,23 +138,41 @@ public ObservableValue> getMatchedGroups() { return matchedGroups; } - public ObservableValue> getSpecialField(SpecialField field) { + public ObservableValue> getSpecialField( + SpecialField field) { OptionalBinding value = specialFieldValues.get(field); // Fetch possibly updated value from BibEntry entry Optional currentValue = this.entry.getField(field); if (value != null) { if (currentValue.isEmpty() && value.getValue().isEmpty()) { - var zeroValue = getField(field).flatMapOpt(fieldValue -> field.parseValue("CLEAR_RANK").map(SpecialFieldValueViewModel::new)); + var zeroValue = + getField(field) + .flatMapOpt( + fieldValue -> + 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)); + value = + getField(field) + .flatMapOpt( + fieldValue -> + field.parseValue(fieldValue) + .map(SpecialFieldValueViewModel::new)); specialFieldValues.put(field, value); } return value; @@ -148,15 +187,17 @@ public ObservableValue getFields(OrFields fields) { ArrayList observables = new ArrayList<>(List.of(entry.getObservables())); observables.add(fieldValueFormatter); - value = Bindings.createStringBinding(() -> - fieldValueFormatter.getValue().formatFieldsValues(fields, entry), - observables.toArray(Observable[]::new)); + value = + Bindings.createStringBinding( + () -> fieldValueFormatter.getValue().formatFieldsValues(fields, entry), + observables.toArray(Observable[]::new)); fieldValues.put(fields, value); return value; } public StringProperty bibDatabasePathProperty() { - return new ReadOnlyStringWrapper(bibDatabaseContext.getDatabasePath().map(Path::toString).orElse("")); + return new ReadOnlyStringWrapper( + bibDatabaseContext.getDatabasePath().map(Path::toString).orElse("")); } public BibDatabaseContext getBibDatabaseContext() { diff --git a/src/main/java/org/jabref/gui/maintable/CellFactory.java b/src/main/java/org/jabref/gui/maintable/CellFactory.java index 19abeff7f004..5f85623c6b41 100644 --- a/src/main/java/org/jabref/gui/maintable/CellFactory.java +++ b/src/main/java/org/jabref/gui/maintable/CellFactory.java @@ -1,10 +1,5 @@ package org.jabref.gui.maintable; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.undo.UndoManager; - import javafx.scene.Node; import org.jabref.gui.externalfiletype.ExternalFileType; @@ -17,6 +12,11 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.undo.UndoManager; + public class CellFactory { private final Map TABLE_ICONS = new HashMap<>(); @@ -55,42 +55,49 @@ public CellFactory(GuiPreferences preferences, UndoManager undoManager) { // icon.setToolTipText(Localization.lang("Open file")); TABLE_ICONS.put(StandardField.FILE, icon); - for (ExternalFileType fileType : preferences.getExternalApplicationsPreferences().getExternalFileTypes()) { + for (ExternalFileType fileType : + preferences.getExternalApplicationsPreferences().getExternalFileTypes()) { icon = fileType.getIcon(); // icon.setToolTipText(Localization.lang("Open %0 file", fileType.getName())); 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); // Ranking item in the menu uses one star - SpecialFieldViewModel rankViewModel = new SpecialFieldViewModel(SpecialField.RANKING, preferences, undoManager); + SpecialFieldViewModel rankViewModel = + new SpecialFieldViewModel(SpecialField.RANKING, preferences, undoManager); icon = rankViewModel.getIcon(); // icon.setToolTipText(rankViewModel.getLocalization()); 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); @@ -102,7 +109,8 @@ public Node getTableIcon(Field field) { // 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 + // node should be generated for each call, as nodes can be added to the scene graph only + // once return icon.getGraphicNode(); } } diff --git a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java index ca000f2ce283..740d3520591c 100644 --- a/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java +++ b/src/main/java/org/jabref/gui/maintable/ColumnPreferences.java @@ -1,20 +1,21 @@ package org.jabref.gui.maintable; -import java.util.List; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import java.util.List; + 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); } diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index b4aa68fd3210..0520b1c95c6d 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -1,14 +1,5 @@ package org.jabref.gui.maintable; -import java.nio.file.Path; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.StringJoiner; -import java.util.concurrent.Callable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -25,10 +16,18 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.StandardField; - import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * SIDE EFFECT: Sets the "cites" field of the entry having the linked files * @@ -50,9 +49,8 @@ public class ExtractReferencesAction extends SimpleCommand { 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); } @@ -62,23 +60,24 @@ public ExtractReferencesAction(DialogService dialogService, * @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; this.entry = entry; this.linkedFile = linkedFile; - bibliographyFromPdfImporter = new BibliographyFromPdfImporter(preferences.getCitationKeyPatternPreferences()); + bibliographyFromPdfImporter = + new BibliographyFromPdfImporter(preferences.getCitationKeyPatternPreferences()); if (this.linkedFile == null) { this.executable.bind( ActionHelper.needsEntriesSelected(stateManager) - .and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager)) - ); + .and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager))); } else { this.setExecutable(true); } @@ -90,48 +89,65 @@ public void execute() { } private void extractReferences() { - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - List selectedEntries; - if (entry == null) { - selectedEntries = stateManager.getSelectedEntries(); - } else { - selectedEntries = List.of(entry); - } + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> { + List selectedEntries; + if (entry == null) { + selectedEntries = stateManager.getSelectedEntries(); + } else { + selectedEntries = List.of(entry); + } - boolean online = this.preferences.getGrobidPreferences().isGrobidEnabled(); - Callable parserResultCallable; - if (online) { - Optional> parserResultCallableOnline = getParserResultCallableOnline(databaseContext, selectedEntries); - if (parserResultCallableOnline.isEmpty()) { - return; - } - parserResultCallable = parserResultCallableOnline.get(); - } else { - parserResultCallable = getParserResultCallableOffline(databaseContext, selectedEntries); - } - BackgroundTask task = BackgroundTask.wrap(parserResultCallable) - .withInitialMessage(Localization.lang("Processing PDF(s)")); + boolean online = + this.preferences.getGrobidPreferences().isGrobidEnabled(); + Callable parserResultCallable; + if (online) { + Optional> parserResultCallableOnline = + getParserResultCallableOnline( + databaseContext, selectedEntries); + if (parserResultCallableOnline.isEmpty()) { + return; + } + parserResultCallable = parserResultCallableOnline.get(); + } else { + parserResultCallable = + getParserResultCallableOffline( + databaseContext, selectedEntries); + } + BackgroundTask task = + BackgroundTask.wrap(parserResultCallable) + .withInitialMessage( + Localization.lang("Processing PDF(s)")); - task.onFailure(dialogService::showErrorDialogAndWait); + task.onFailure(dialogService::showErrorDialogAndWait); - ImportEntriesDialog dialog = new ImportEntriesDialog(stateManager.getActiveDatabase().get(), task); - String title; - if (online) { - title = Localization.lang("Extract References (online)"); - } else { - title = Localization.lang("Extract References (offline)"); - } - dialog.setTitle(title); - dialogService.showCustomDialogAndWait(dialog); - }); + ImportEntriesDialog dialog = + new ImportEntriesDialog( + stateManager.getActiveDatabase().get(), task); + String title; + if (online) { + title = Localization.lang("Extract References (online)"); + } else { + title = Localization.lang("Extract References (offline)"); + } + dialog.setTitle(title); + dialogService.showCustomDialogAndWait(dialog); + }); } - 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 +160,11 @@ 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); } @@ -153,9 +173,15 @@ private void extractReferences() { }; } - private void extractReferences(Iterator fileListIterator, ParserResult result, BibEntry currentEntry) { + 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); @@ -181,14 +207,17 @@ private static String getCites(List entries, BibEntry currentEntry) { } 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 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("")); + Matcher matcher = + pattern.matcher(importedEntry.getField(StandardField.COMMENT).orElse("")); if (matcher.hasMatch()) { newCitationKey = sourceCitationKey + "-" + matcher.group(1); } else { @@ -202,18 +231,31 @@ 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"), - Localization.lang("You are about to process %0 files. Continue?", fileList.size()), - Localization.lang("Continue"), Localization.lang("Cancel")); + 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/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index 4a94fd56b5f8..e52fb4ba9576 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -1,14 +1,7 @@ package org.jabref.gui.maintable; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; +import com.google.common.eventbus.Subscribe; import javafx.collections.ListChangeListener; import javafx.css.PseudoClass; @@ -54,20 +47,31 @@ import org.jabref.model.database.event.EntriesAddedEvent; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; - -import com.airhacks.afterburner.injection.Injector; -import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + @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; @@ -83,18 +87,19 @@ public class MainTable extends TableView { private long lastKeyPressTime; private String columnSearchTerm; - 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; @@ -105,7 +110,11 @@ 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(); @@ -116,39 +125,61 @@ 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)) - .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)) + .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)) + .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) @@ -158,23 +189,36 @@ public MainTable(MainTableDataModel model, 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()); - } - }); - - 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); - })); + this.getSortOrder() + .addListener( + (ListChangeListener>) + change -> { + 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); + })); if (mainTablePreferences.getResizeColumnsToFit()) { this.setColumnResizePolicy(new SmartConstrainedResizePolicy()); @@ -188,22 +232,25 @@ public MainTable(MainTableDataModel model, model.getEntriesFilteredAndSorted().comparatorProperty().bind(this.comparatorProperty()); // Store visual state - new PersistenceVisualStateTable(this, mainTablePreferences.getColumnPreferences()).addListeners(); + new PersistenceVisualStateTable(this, mainTablePreferences.getColumnPreferences()) + .addListeners(); setupKeyBindings(keyBindingRepository); - this.setOnKeyTyped(key -> { - if (this.getSortOrder().size() <= 1) { - return; - } - // skip match category column - this.jumpToSearchKey(getSortOrder().get(1), key); - }); + this.setOnKeyTyped( + key -> { + if (this.getSortOrder().size() <= 1) { + return; + } + // skip match category column + this.jumpToSearchKey(getSortOrder().get(1), key); + }); database.getDatabase().registerListener(this); // Enable the header right-click menu. - new MainTableHeaderContextMenu(this, mainTableColumnFactory, tabContainer, dialogService).show(true); + new MainTableHeaderContextMenu(this, mainTableColumnFactory, tabContainer, dialogService) + .show(true); } /** @@ -213,8 +260,12 @@ public MainTable(MainTableDataModel model, * @param sortedColumn The sorted column in {@link MainTable} * @param keyEvent The pressed character */ - private void jumpToSearchKey(TableColumn sortedColumn, KeyEvent keyEvent) { - if (keyEvent.isAltDown() || keyEvent.isControlDown() || keyEvent.isMetaDown() || keyEvent.isShiftDown()) { + private void jumpToSearchKey( + TableColumn sortedColumn, KeyEvent keyEvent) { + if (keyEvent.isAltDown() + || keyEvent.isControlDown() + || keyEvent.isMetaDown() + || keyEvent.isShiftDown()) { return; } if ((keyEvent.getCharacter() == null) || (sortedColumn == null)) { @@ -230,17 +281,23 @@ private void jumpToSearchKey(TableColumn sortedColumn lastKeyPressTime = System.currentTimeMillis(); this.getItems().stream() - .filter(item -> Optional.ofNullable(sortedColumn.getCellObservableValue(item).getValue()) - .map(Object::toString) - .orElse("") - .toLowerCase() - .startsWith(columnSearchTerm)) - .findFirst() - .ifPresent(item -> { - getSelectionModel().clearSelection(); - getSelectionModel().select(item); - scrollTo(item); - }); + .filter( + item -> + Optional.ofNullable( + sortedColumn + .getCellObservableValue(item) + .getValue()) + .map(Object::toString) + .orElse("") + .toLowerCase() + .startsWith(columnSearchTerm)) + .findFirst() + .ifPresent( + item -> { + getSelectionModel().clearSelection(); + getSelectionModel().select(item); + scrollTo(item); + }); } @Subscribe @@ -250,10 +307,12 @@ public void listen(EntriesAddedEvent event) { public void clearAndSelect(BibEntry bibEntry) { getSelectionModel().clearSelection(); - findEntry(bibEntry).ifPresent(entry -> { - getSelectionModel().select(entry); - scrollTo(entry); - }); + findEntry(bibEntry) + .ifPresent( + entry -> { + getSelectionModel().select(entry); + scrollTo(entry); + }); } private void scrollToNextMatchCategory() { @@ -296,60 +355,67 @@ private void scrollToPreviousMatchCategory() { } 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); - - this.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.ENTER) { - getSelectedEntries().stream() - .findFirst() - .ifPresent(libraryTab::showAndEdit); - event.consume(); - return; - } - - Optional keyBinding = keyBindings.mapToKeyBinding(event); - if (keyBinding.isPresent()) { - switch (keyBinding.get()) { - case SELECT_FIRST_ENTRY: - clearAndSelectFirst(); + 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); + + this.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.ENTER) { + getSelectedEntries().stream() + .findFirst() + .ifPresent(libraryTab::showAndEdit); event.consume(); - break; - case SELECT_LAST_ENTRY: - clearAndSelectLast(); - event.consume(); - break; - case PASTE: - pasteAction.execute(); - event.consume(); - break; - case COPY: - copyAction.execute(); - event.consume(); - break; - case CUT: - cutAction.execute(); - event.consume(); - break; - case DELETE_ENTRY: - deleteAction.execute(); - event.consume(); - break; - case SCROLL_TO_NEXT_MATCH_CATEGORY: - scrollToNextMatchCategory(); - event.consume(); - break; - case SCROLL_TO_PREVIOUS_MATCH_CATEGORY: - scrollToPreviousMatchCategory(); - event.consume(); - break; - default: - // Pass other keys to parent - } - } - }); + return; + } + + Optional keyBinding = keyBindings.mapToKeyBinding(event); + if (keyBinding.isPresent()) { + switch (keyBinding.get()) { + case SELECT_FIRST_ENTRY: + clearAndSelectFirst(); + event.consume(); + break; + case SELECT_LAST_ENTRY: + clearAndSelectLast(); + event.consume(); + break; + case PASTE: + pasteAction.execute(); + event.consume(); + break; + case COPY: + copyAction.execute(); + event.consume(); + break; + case CUT: + cutAction.execute(); + event.consume(); + break; + case DELETE_ENTRY: + deleteAction.execute(); + event.consume(); + break; + case SCROLL_TO_NEXT_MATCH_CATEGORY: + scrollToNextMatchCategory(); + event.consume(); + break; + case SCROLL_TO_PREVIOUS_MATCH_CATEGORY: + scrollToPreviousMatchCategory(); + event.consume(); + break; + default: + // Pass other keys to parent + } + } + }); } public void clearAndSelectFirst() { @@ -364,7 +430,8 @@ private void clearAndSelectLast() { scrollTo(getItems().size() - 1); } - private void handleOnDragOver(TableRow row, BibEntryTableViewModel item, DragEvent event) { + private void handleOnDragOver( + TableRow row, BibEntryTableViewModel item, DragEvent event) { if (event.getDragboard().hasFiles()) { event.acceptTransferModes(TransferMode.ANY); ControlHelper.setDroppingPseudoClasses(row, event); @@ -379,28 +446,44 @@ 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(); + 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); + content = + clipboardContentGenerator.generate( + entries, CitationStyleOutputFormat.HTML, database); } catch (IOException e) { LOGGER.warn("Could not generate clipboard content. Falling back to empty clipboard", e); content = new ClipboardContent(); @@ -419,18 +502,25 @@ 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()) { - List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); + List files = + event.getDragboard().getFiles().stream() + .map(File::toPath) + .collect(Collectors.toList()); // Different actions depending on where the user releases the drop in the target row // Bottom + top -> import entries // Center -> link files to entry // Depending on the pressed modifier, move/copy/link files to drop target switch (ControlHelper.getDroppingMouseLocation(row, event)) { - case TOP, BOTTOM -> importHandler.importFilesInBackground(files, database, filePreferences).executeWith(taskExecutor); + case TOP, BOTTOM -> + importHandler + .importFilesInBackground(files, database, filePreferences) + .executeWith(taskExecutor); case CENTER -> { BibEntry entry = target.getEntry(); switch (event.getTransferMode()) { @@ -440,11 +530,17 @@ private void handleOnDragDropped(TableRow row, BibEntryT } case MOVE -> { LOGGER.debug("Mode MOVE"); // alt on win - importHandler.getLinker().moveFilesToFileDirRenameAndAddToEntry(entry, files, libraryTab.getLuceneManager()); + importHandler + .getLinker() + .moveFilesToFileDirRenameAndAddToEntry( + entry, files, libraryTab.getLuceneManager()); } case COPY -> { LOGGER.debug("Mode Copy"); // ctrl on win - importHandler.getLinker().copyFilesToFileDirAndAddToEntry(entry, files, libraryTab.getLuceneManager()); + importHandler + .getLinker() + .copyFilesToFileDirAndAddToEntry( + entry, files, libraryTab.getLuceneManager()); } } } @@ -462,7 +558,9 @@ 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).executeWith(taskExecutor); + importHandler + .importFilesInBackground(files, this.database, filePreferences) + .executeWith(taskExecutor); success = true; } @@ -479,17 +577,14 @@ public MainTableDataModel getTableModel() { } public List getSelectedEntries() { - return getSelectionModel() - .getSelectedItems() - .stream() + return getSelectionModel().getSelectedItems().stream() .map(BibEntryTableViewModel::getEntry) .collect(Collectors.toList()); } private Optional findEntry(BibEntry entry) { - return model.getEntriesFilteredAndSorted() - .stream() - .filter(viewModel -> viewModel.getEntry().equals(entry)) - .findFirst(); + return model.getEntriesFilteredAndSorted().stream() + .filter(viewModel -> viewModel.getEntry().equals(entry)) + .findFirst(); } } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java index bd592f6b22ca..98b63627d19d 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnFactory.java @@ -1,13 +1,6 @@ package org.jabref.gui.maintable; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.geometry.Insets; @@ -45,11 +38,18 @@ import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.SpecialField; import org.jabref.model.groups.AbstractGroup; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + public class MainTableColumnFactory { public static final String STYLE_ICON_COLUMN = "column-icon"; @@ -65,13 +65,14 @@ public class MainTableColumnFactory { 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; @@ -81,7 +82,9 @@ public MainTableColumnFactory(BibDatabaseContext database, this.undoManager = undoManager; this.stateManager = stateManager; ThemeManager themeManager = Injector.instantiateModelOrService(ThemeManager.class); - this.tooltip = new MainTableTooltip(database, dialogService, preferences, themeManager, taskExecutor); + this.tooltip = + new MainTableTooltip( + database, dialogService, preferences, themeManager, taskExecutor); } public TableColumn createColumn(MainTableColumnModel column) { @@ -119,7 +122,9 @@ public MainTableColumnFactory(BibDatabaseContext database, if (field instanceof SpecialField) { returnColumn = createSpecialFieldColumn(column); } else { - LOGGER.warn("Special field type '{}' is unknown. Using normal column type.", column.getQualifier()); + LOGGER.warn( + "Special field type '{}' is unknown. Using normal column type.", + column.getQualifier()); returnColumn = createFieldColumn(column, tooltip); } } @@ -137,7 +142,9 @@ public MainTableColumnFactory(BibDatabaseContext database, public List> createColumns() { List> columns = new ArrayList<>(); - columns.add(createMatchCategoryColumn(new MainTableColumnModel(MainTableColumnModel.Type.MATCH_CATEGORY))); + columns.add( + createMatchCategoryColumn( + new MainTableColumnModel(MainTableColumnModel.Type.MATCH_CATEGORY))); columnPreferences.getColumns().forEach(column -> columns.add(createColumn(column))); return columns; } @@ -154,8 +161,10 @@ public static void setExactWidth(TableColumn column, double width) { * in the floating mode. The order of the {@link MatchCategory} enum constants * determines the sorting order.

    */ - private TableColumn createMatchCategoryColumn(MainTableColumnModel columnModel) { - TableColumn column = new MainTableColumn<>(columnModel); + private TableColumn createMatchCategoryColumn( + MainTableColumnModel columnModel) { + TableColumn column = + new MainTableColumn<>(columnModel); column.setCellValueFactory(cellData -> cellData.getValue().matchCategory()); column.setSortable(true); column.setSortType(TableColumn.SortType.ASCENDING); @@ -163,33 +172,45 @@ private TableColumn createMatchCategoryCo return column; } - private TableColumn createScoreColumn(MainTableColumnModel columnModel) { + private TableColumn createScoreColumn( + MainTableColumnModel columnModel) { TableColumn column = new MainTableColumn<>(columnModel); Node header = new Text(Localization.lang("Score")); header.getStyleClass().add("mainTable-header"); - Tooltip.install(header, new Tooltip(MainTableColumnModel.Type.MATCH_SCORE.getDisplayName())); + Tooltip.install( + header, new Tooltip(MainTableColumnModel.Type.MATCH_SCORE.getDisplayName())); column.setGraphic(header); column.setStyle("-fx-alignment: CENTER-RIGHT;"); column.setCellValueFactory(cellData -> cellData.getValue().searchScoreProperty()); - new ValueTableCellFactory().withText(String::valueOf).install(column); + new ValueTableCellFactory() + .withText(String::valueOf) + .install(column); column.setSortable(true); column.setReorderable(false); - column.visibleProperty().bind(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).isPresent()); + column.visibleProperty() + .bind(stateManager.activeSearchQuery(SearchType.NORMAL_SEARCH).isPresent()); return column; } /** * Creates a column with a continuous number */ - private TableColumn createIndexColumn(MainTableColumnModel columnModel) { + private TableColumn createIndexColumn( + MainTableColumnModel columnModel) { TableColumn column = new MainTableColumn<>(columnModel); Node header = new Text("#"); header.getStyleClass().add("mainTable-header"); Tooltip.install(header, new Tooltip(MainTableColumnModel.Type.INDEX.getDisplayName())); column.setGraphic(header); column.setStyle("-fx-alignment: CENTER-RIGHT;"); - column.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<>( - String.valueOf(cellData.getTableView().getItems().indexOf(cellData.getValue()) + 1))); + column.setCellValueFactory( + cellData -> + new ReadOnlyObjectWrapper<>( + String.valueOf( + cellData.getTableView() + .getItems() + .indexOf(cellData.getValue()) + + 1))); new ValueTableCellFactory() .withText(text -> text) .install(column); @@ -200,8 +221,10 @@ private TableColumn createIndexColumn(MainTableC /** * Creates a column for group color bars. */ - private TableColumn createGroupColumn(MainTableColumnModel columnModel) { - TableColumn> column = new MainTableColumn<>(columnModel); + private TableColumn createGroupColumn( + MainTableColumnModel columnModel) { + TableColumn> column = + new MainTableColumn<>(columnModel); Node headerGraphic = IconTheme.JabRefIcons.DEFAULT_GROUP_ICON.getGraphicNode(); Tooltip.install(headerGraphic, new Tooltip(Localization.lang("Group color"))); column.setGraphic(headerGraphic); @@ -220,10 +243,13 @@ private TableColumn createIndexColumn(MainTableC /** * Creates a column for group icons */ - private TableColumn createGroupIconColumn(MainTableColumnModel columnModel) { - TableColumn> column = new MainTableColumn<>(columnModel); + private TableColumn createGroupIconColumn( + MainTableColumnModel columnModel) { + TableColumn> column = + new MainTableColumn<>(columnModel); Node headerGraphic = IconTheme.JabRefIcons.DEFAULT_GROUP_ICON_COLUMN.getGraphicNode(); - Tooltip.install(headerGraphic, new Tooltip(MainTableColumnModel.Type.GROUP_ICONS.getDisplayName())); + Tooltip.install( + headerGraphic, new Tooltip(MainTableColumnModel.Type.GROUP_ICONS.getDisplayName())); column.setGraphic(headerGraphic); column.getStyleClass().add(STYLE_ICON_COLUMN); column.setResizable(true); @@ -236,10 +262,10 @@ private TableColumn createIndexColumn(MainTableC return column; } - private Node createGroupColorRegion(BibEntryTableViewModel entry, List matchedGroups) { - List groupColors = matchedGroups.stream() - .flatMap(group -> group.getColor().stream()) - .toList(); + private Node createGroupColorRegion( + BibEntryTableViewModel entry, List matchedGroups) { + List groupColors = + matchedGroups.stream().flatMap(group -> group.getColor().stream()).toList(); if (!groupColors.isEmpty()) { HBox container = new HBox(); @@ -248,33 +274,48 @@ private Node createGroupColorRegion(BibEntryTableViewModel entry, List { - Rectangle groupRectangle = new Rectangle(); - groupRectangle.getStyleClass().add("groupColumnBackground"); - groupRectangle.setWidth(3); - groupRectangle.setHeight(18); - groupRectangle.setFill(groupColor); - groupRectangle.setStrokeWidth(1); - container.getChildren().add(groupRectangle); - }); + groupColors.stream() + .distinct() + .forEach( + groupColor -> { + Rectangle groupRectangle = new Rectangle(); + groupRectangle.getStyleClass().add("groupColumnBackground"); + groupRectangle.setWidth(3); + groupRectangle.setHeight(18); + groupRectangle.setFill(groupColor); + groupRectangle.setStrokeWidth(1); + container.getChildren().add(groupRectangle); + }); - 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); + 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); Tooltip.install(container, tooltip); return container; } return new Pane(); } - private Node createGroupIconRegion(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(); + private Node createGroupIconRegion( + 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(); if (!groupIcons.isEmpty()) { HBox container = new HBox(); container.setSpacing(2); @@ -282,15 +323,23 @@ 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); + 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); Tooltip.install(container, tooltip); return container; } @@ -300,21 +349,25 @@ 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) { - return new LinkedIdentifierColumn(columnModel, cellFactory, database, dialogService, preferences, stateManager); + 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); } @@ -322,19 +375,18 @@ private TableColumn * 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); + private TableColumn> createFilesColumn( + MainTableColumnModel columnModel) { + 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, + private TableColumn> createExtraFileColumn( + MainTableColumnModel columnModel) { + return new FileColumn( + columnModel, database, dialogService, preferences, @@ -345,7 +397,8 @@ private TableColumn> createExtraFileCol /** * Create library column containing the Filename of the library's bib file */ - private TableColumn createLibraryColumn(MainTableColumnModel columnModel) { + private TableColumn createLibraryColumn( + MainTableColumnModel columnModel) { return new LibraryColumn(columnModel); } } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java b/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java index d0d5f7366dec..8a50913b1f56 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableColumnModel.java @@ -1,10 +1,6 @@ package org.jabref.gui.maintable; -import java.util.EnumSet; -import java.util.List; -import java.util.Objects; - -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.injection.Injector; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -20,11 +16,15 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.metadata.SaveOrder; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; + +import javax.swing.undo.UndoManager; + /** * 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. @@ -34,6 +34,7 @@ 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 MATCH_SCORE("match_score", Localization.lang("Match score")), @@ -48,11 +49,12 @@ public enum Type { 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) { this.name = name; this.displayName = name; @@ -85,7 +87,8 @@ public static Type fromString(String text) { 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 ObjectProperty sortTypeProperty = + new SimpleObjectProperty<>(); private final CliPreferences preferences; private final UndoManager undoManager; @@ -147,19 +150,27 @@ public String getName() { if (qualifierProperty.getValue().isBlank()) { return typeProperty.getValue().getName(); } else { - return typeProperty.getValue().getName() + COLUMNS_QUALIFIER_DELIMITER + qualifierProperty.getValue(); + return typeProperty.getValue().getName() + + COLUMNS_QUALIFIER_DELIMITER + + qualifierProperty.getValue(); } } public String getDisplayName() { - if ((Type.ICON_COLUMNS.contains(typeProperty.getValue()) && qualifierProperty.getValue().isBlank()) - || (typeProperty.getValue() == Type.INDEX) || typeProperty.getValue() == Type.MATCH_SCORE) { + if ((Type.ICON_COLUMNS.contains(typeProperty.getValue()) + && qualifierProperty.getValue().isBlank()) + || (typeProperty.getValue() == Type.INDEX) + || typeProperty.getValue() == Type.MATCH_SCORE) { return typeProperty.getValue().getDisplayName(); } else { - // In case an OrField is used, `FieldFactory.parseField` returns UnknownField, which leads to + // 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); } } @@ -234,11 +245,10 @@ 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 + qualifier = + splittedName[0]; // By default the rawColumnName is parsed as NORMALFIELD } else { qualifier = splittedName[1]; } diff --git a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java index 5339d4220b76..a163f0266af4 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java @@ -1,7 +1,8 @@ package org.jabref.gui.maintable; -import java.util.List; -import java.util.Optional; +import com.google.common.eventbus.Subscribe; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; import javafx.beans.binding.Bindings; import javafx.beans.property.IntegerProperty; @@ -35,18 +36,18 @@ import org.jabref.model.search.event.IndexStartedEvent; import org.jabref.model.search.matchers.MatcherSet; import org.jabref.model.search.matchers.MatcherSets; - -import com.google.common.eventbus.Subscribe; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.Subscription; import org.jspecify.annotations.Nullable; +import java.util.List; +import java.util.Optional; + public class MainTableDataModel { private final ObservableList entriesViewModel; private final FilteredList entriesFiltered; private final SortedList entriesFilteredAndSorted; - private final ObjectProperty fieldValueFormatter = new SimpleObjectProperty<>(); + private final ObjectProperty fieldValueFormatter = + new SimpleObjectProperty<>(); private final GroupsPreferences groupsPreferences; private final SearchPreferences searchPreferences; private final NameDisplayPreferences nameDisplayPreferences; @@ -63,14 +64,15 @@ public class MainTableDataModel { private Optional groupsMatcher; - public MainTableDataModel(BibDatabaseContext context, - GuiPreferences preferences, - TaskExecutor taskExecutor, - StateManager stateManager, - @Nullable LuceneManager luceneManager, - ListProperty selectedGroupsProperty, - OptionalObjectProperty searchQueryProperty, - IntegerProperty resultSizeProperty) { + public MainTableDataModel( + BibDatabaseContext context, + GuiPreferences preferences, + TaskExecutor taskExecutor, + StateManager stateManager, + @Nullable LuceneManager luceneManager, + ListProperty selectedGroupsProperty, + OptionalObjectProperty searchQueryProperty, + IntegerProperty resultSizeProperty) { this.groupsPreferences = preferences.getGroupsPreferences(); this.searchPreferences = preferences.getSearchPreferences(); this.nameDisplayPreferences = preferences.getNameDisplayPreferences(); @@ -85,56 +87,93 @@ public MainTableDataModel(BibDatabaseContext context, this.bibDatabaseContext.getDatabase().registerListener(indexUpdatedListener); resetFieldFormatter(); - ObservableList allEntries = BindingsHelper.forUI(context.getDatabase().getEntries()); - entriesViewModel = EasyBind.mapBacked(allEntries, entry -> new BibEntryTableViewModel(entry, bibDatabaseContext, fieldValueFormatter), false); + ObservableList allEntries = + BindingsHelper.forUI(context.getDatabase().getEntries()); + 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())); + 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()))); + 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); } private void updateSearchMatches(Optional query) { - BackgroundTask.wrap(() -> { - if (query.isPresent()) { - SearchResults results = luceneManager.search(query.get()); - setSearchMatches(results); - } else { - clearSearchMatches(); - } - }).onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)).executeWith(taskExecutor); + BackgroundTask.wrap( + () -> { + if (query.isPresent()) { + SearchResults results = luceneManager.search(query.get()); + setSearchMatches(results); + } else { + clearSearchMatches(); + } + }) + .onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)) + .executeWith(taskExecutor); } private void setSearchMatches(SearchResults results) { - boolean isFloatingMode = searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FLOAT; - entriesViewModel.forEach(entry -> { - entry.searchScoreProperty().set(results.getSearchScoreForEntry(entry.getEntry())); - entry.hasFullTextResultsProperty().set(results.hasFulltextResults(entry.getEntry())); - updateEntrySearchMatch(entry, entry.searchScoreProperty().get() > 0, isFloatingMode); - }); + boolean isFloatingMode = + searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FLOAT; + entriesViewModel.forEach( + entry -> { + entry.searchScoreProperty() + .set(results.getSearchScoreForEntry(entry.getEntry())); + entry.hasFullTextResultsProperty() + .set(results.hasFulltextResults(entry.getEntry())); + updateEntrySearchMatch( + entry, entry.searchScoreProperty().get() > 0, isFloatingMode); + }); } private void clearSearchMatches() { - boolean isFloatingMode = searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FLOAT; - entriesViewModel.forEach(entry -> { - entry.searchScoreProperty().set(0); - entry.hasFullTextResultsProperty().set(false); - updateEntrySearchMatch(entry, true, isFloatingMode); - }); + boolean isFloatingMode = + searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FLOAT; + entriesViewModel.forEach( + entry -> { + entry.searchScoreProperty().set(0); + entry.hasFullTextResultsProperty().set(false); + updateEntrySearchMatch(entry, true, isFloatingMode); + }); } - 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 { @@ -143,24 +182,53 @@ 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)); - }).onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)).executeWith(taskExecutor); + BackgroundTask.wrap( + () -> { + boolean isFloatingMode = mode == SearchDisplayMode.FLOAT; + entriesViewModel.forEach( + entry -> + setEntrySearchVisibility( + entry, + entry.isMatchedBySearch().get(), + isFloatingMode)); + }) + .onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)) + .executeWith(taskExecutor); } private void updateGroupMatches(ObservableList groups) { - BackgroundTask.wrap(() -> { - 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)); - }).onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)).executeWith(taskExecutor); + BackgroundTask.wrap( + () -> { + 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)); + }) + .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) { @@ -170,16 +238,18 @@ private void updateEntryGroupMatch(BibEntryTableViewModel entry, 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()); @@ -201,43 +271,105 @@ public SortedList getEntriesFilteredAndSorted() { } public void resetFieldFormatter() { - this.fieldValueFormatter.setValue(new MainTableFieldValueFormatter(nameDisplayPreferences, bibDatabaseContext)); + this.fieldValueFormatter.setValue( + new MainTableFieldValueFormatter(nameDisplayPreferences, bibDatabaseContext)); } class LuceneIndexListener { @Subscribe public void listen(IndexAddedOrUpdatedEvent indexAddedOrUpdatedEvent) { - indexAddedOrUpdatedEvent.entries().forEach(entry -> { - BackgroundTask.wrap(() -> { - int index = bibDatabaseContext.getDatabase().indexOf(entry); - if (index >= 0) { - BibEntryTableViewModel viewModel = entriesViewModel.get(index); - boolean isFloatingMode = searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FLOAT; - boolean isMatched = true; - if (searchQueryProperty.get().isPresent()) { - SearchQuery searchQuery = searchQueryProperty.get().get(); - String newSearchExpression = "+" + SearchFieldConstants.ENTRY_ID + ":" + entry.getId() + " +" + searchQuery.getSearchExpression(); - SearchQuery entryQuery = new SearchQuery(newSearchExpression, searchQuery.getSearchFlags()); - SearchResults results = luceneManager.search(entryQuery); - - viewModel.searchScoreProperty().set(results.getSearchScoreForEntry(entry)); - viewModel.hasFullTextResultsProperty().set(results.hasFulltextResults(entry)); - isMatched = viewModel.searchScoreProperty().get() > 0; - } else { - viewModel.searchScoreProperty().set(0); - viewModel.hasFullTextResultsProperty().set(false); - } - - updateEntrySearchMatch(viewModel, isMatched, isFloatingMode); - updateEntryGroupMatch(viewModel, groupsMatcher, groupsPreferences.getGroupViewMode().contains(GroupViewMode.INVERT), !groupsPreferences.getGroupViewMode().contains(GroupViewMode.FILTER)); - } - return index; - }).onSuccess(index -> { - if (index >= 0) { - FilteredListProxy.refilterListReflection(entriesFiltered, index, index + 1); - } - }).executeWith(taskExecutor); - }); + indexAddedOrUpdatedEvent + .entries() + .forEach( + entry -> { + BackgroundTask.wrap( + () -> { + int index = + bibDatabaseContext + .getDatabase() + .indexOf(entry); + if (index >= 0) { + BibEntryTableViewModel viewModel = + entriesViewModel.get(index); + boolean isFloatingMode = + searchPreferences + .getSearchDisplayMode() + == SearchDisplayMode.FLOAT; + boolean isMatched = true; + if (searchQueryProperty.get().isPresent()) { + SearchQuery searchQuery = + searchQueryProperty.get().get(); + String newSearchExpression = + "+" + + SearchFieldConstants + .ENTRY_ID + + ":" + + entry.getId() + + " +" + + searchQuery + .getSearchExpression(); + SearchQuery entryQuery = + new SearchQuery( + newSearchExpression, + searchQuery + .getSearchFlags()); + SearchResults results = + luceneManager.search( + entryQuery); + + viewModel + .searchScoreProperty() + .set( + results + .getSearchScoreForEntry( + entry)); + viewModel + .hasFullTextResultsProperty() + .set( + results + .hasFulltextResults( + entry)); + isMatched = + viewModel + .searchScoreProperty() + .get() + > 0; + } else { + viewModel.searchScoreProperty().set(0); + viewModel + .hasFullTextResultsProperty() + .set(false); + } + + updateEntrySearchMatch( + viewModel, + isMatched, + isFloatingMode); + updateEntryGroupMatch( + viewModel, + groupsMatcher, + groupsPreferences + .getGroupViewMode() + .contains( + GroupViewMode + .INVERT), + !groupsPreferences + .getGroupViewMode() + .contains( + GroupViewMode + .FILTER)); + } + return index; + }) + .onSuccess( + index -> { + if (index >= 0) { + FilteredListProxy.refilterListReflection( + entriesFiltered, index, index + 1); + } + }) + .executeWith(taskExecutor); + }); } @Subscribe diff --git a/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java b/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java index 33841527a34f..91a4f02ad0e0 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableFieldValueFormatter.java @@ -1,6 +1,7 @@ package org.jabref.gui.maintable; -import java.util.Optional; +import static org.jabref.gui.maintable.NameDisplayPreferences.AbbreviationStyle; +import static org.jabref.gui.maintable.NameDisplayPreferences.DisplayStyle; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; @@ -10,15 +11,15 @@ import org.jabref.model.entry.field.FieldProperty; import org.jabref.model.entry.field.OrFields; -import static org.jabref.gui.maintable.NameDisplayPreferences.AbbreviationStyle; -import static org.jabref.gui.maintable.NameDisplayPreferences.DisplayStyle; +import java.util.Optional; 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(); @@ -34,14 +35,16 @@ public MainTableFieldValueFormatter(NameDisplayPreferences nameDisplayPreference */ public String formatFieldsValues(final OrFields fields, final BibEntry entry) { for (Field field : fields.getFields()) { - if (field.getProperties().contains(FieldProperty.PERSON_NAMES) && (displayStyle != DisplayStyle.AS_IS)) { + if (field.getProperties().contains(FieldProperty.PERSON_NAMES) + && (displayStyle != DisplayStyle.AS_IS)) { Optional name = entry.getResolvedFieldOrAlias(field, bibDatabase); if (name.isPresent()) { return formatFieldWithAuthorValue(name.get()); } } else { - Optional content = entry.getResolvedFieldOrAliasLatexFree(field, bibDatabase); + Optional content = + entry.getResolvedFieldOrAliasLatexFree(field, bibDatabase); if (content.isPresent()) { return content.get(); @@ -66,19 +69,21 @@ private String formatFieldWithAuthorValue(final String nameToFormat) { AuthorList authors = AuthorList.parse(nameToFormat); if (((displayStyle == DisplayStyle.FIRSTNAME_LASTNAME) - || (displayStyle == DisplayStyle.LASTNAME_FIRSTNAME)) + || (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/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java b/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java index 7cfe366ff12e..7b78d718bddb 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableHeaderContextMenu.java @@ -1,8 +1,5 @@ package org.jabref.gui.maintable; -import java.util.ArrayList; -import java.util.List; - import javafx.collections.ObservableList; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; @@ -20,6 +17,9 @@ import org.jabref.gui.preferences.table.TableTab; import org.jabref.logic.l10n.Localization; +import java.util.ArrayList; +import java.util.List; + public class MainTableHeaderContextMenu extends ContextMenu { private static final int OUT_OF_BOUNDS = -1; @@ -28,10 +28,11 @@ public class MainTableHeaderContextMenu extends ContextMenu { 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,16 +46,18 @@ 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. - 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()) { - this.hide(); - } - event.consume(); - }); + // 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()) { + this.hide(); + } + event.consume(); + }); } /** @@ -67,7 +70,8 @@ private void constructItems() { // Populate the menu with currently used fields for (TableColumn column : mainTable.getColumns()) { - if (((MainTableColumn) column).getModel().getType() == MainTableColumnModel.Type.MATCH_CATEGORY) { + if (((MainTableColumn) column).getModel().getType() + == MainTableColumnModel.Type.MATCH_CATEGORY) { continue; } // Append only if the column has not already been added (a common column) @@ -77,7 +81,11 @@ 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())); } } @@ -93,16 +101,18 @@ private void constructItems() { this.getItems().add(new SeparatorMenuItem()); ActionFactory actionfactory = new ActionFactory(); - MenuItem showMoreItem = actionfactory.createMenuItem( - StandardActions.SHOW_PREFS.withText(Localization.lang("More options...")), - new ShowPreferencesAction(tabContainer, TableTab.class, dialogService)); + MenuItem showMoreItem = + actionfactory.createMenuItem( + StandardActions.SHOW_PREFS.withText(Localization.lang("More options...")), + new ShowPreferencesAction(tabContainer, TableTab.class, dialogService)); this.getItems().add(showMoreItem); } /** * Creates an item for the menu constructed with the name/visibility of the table column. */ - private RightClickMenuItem createMenuItem(TableColumn column, boolean isDisplaying) { + private RightClickMenuItem createMenuItem( + TableColumn column, boolean isDisplaying) { // Gets display name and constructs Radio Menu Item. MainTableColumn tableColumn = (MainTableColumn) column; String displayName = tableColumn.getDisplayName(); @@ -139,7 +149,13 @@ 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 +168,10 @@ 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; @@ -182,18 +200,31 @@ 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); } @@ -216,16 +247,17 @@ private class RightClickMenuItem extends RadioMenuItem { setIndex(OUT_OF_BOUNDS); // Set action to toggle visibility from main table when item is clicked - this.setOnAction(event -> { - if (isVisibleInTable()) { - setIndex(obtainIndexOfColumn(column)); - removeColumn(column); - } else { - addColumn(column, this.index); - setIndex(obtainIndexOfColumn(column)); - } - setVisibleInTable(!this.visibleInTable); - }); + this.setOnAction( + event -> { + if (isVisibleInTable()) { + setIndex(obtainIndexOfColumn(column)); + removeColumn(column); + } else { + addColumn(column, this.index); + setIndex(obtainIndexOfColumn(column)); + } + setVisibleInTable(!this.visibleInTable); + }); } public void setIndex(int index) { diff --git a/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java b/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java index f2f42a625036..4aee66780995 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java +++ b/src/main/java/org/jabref/gui/maintable/MainTablePreferences.java @@ -8,9 +8,10 @@ public class MainTablePreferences { 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); diff --git a/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java b/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java index 941f96dcbd36..fb102001cbbe 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableTooltip.java @@ -20,9 +20,16 @@ public class MainTableTooltip extends Tooltip { private final VBox tooltipContent = new VBox(); private final Label fieldValueLabel = new Label(); - public MainTableTooltip(BibDatabaseContext databaseContext, DialogService dialogService, GuiPreferences preferences, ThemeManager themeManager, TaskExecutor taskExecutor) { + public MainTableTooltip( + BibDatabaseContext databaseContext, + DialogService dialogService, + GuiPreferences preferences, + ThemeManager themeManager, + TaskExecutor taskExecutor) { this.preferences = preferences; - this.preview = new PreviewViewer(databaseContext, dialogService, preferences, themeManager, taskExecutor); + this.preview = + new PreviewViewer( + databaseContext, dialogService, preferences, themeManager, taskExecutor); this.setShowDelay(Duration.seconds(1)); this.tooltipContent.getChildren().addAll(fieldValueLabel, preview); } diff --git a/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java b/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java index 8ffc1b2032f1..300cd0335d96 100644 --- a/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java +++ b/src/main/java/org/jabref/gui/maintable/NameDisplayPreferences.java @@ -6,18 +6,23 @@ public class NameDisplayPreferences { public enum DisplayStyle { - NATBIB, AS_IS, FIRSTNAME_LASTNAME, LASTNAME_FIRSTNAME + NATBIB, + AS_IS, + FIRSTNAME_LASTNAME, + LASTNAME_FIRSTNAME } public enum AbbreviationStyle { - NONE, LASTNAME_ONLY, FULL + NONE, + LASTNAME_ONLY, + FULL } private final ObjectProperty displayStyle = new SimpleObjectProperty<>(); - private final ObjectProperty abbreviationStyle = 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); } diff --git a/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java b/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java index 96336a575740..449e98c3345d 100644 --- a/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java +++ b/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfAction.java @@ -1,8 +1,5 @@ package org.jabref.gui.maintable; -import java.nio.file.Path; -import java.util.concurrent.Callable; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; @@ -15,10 +12,12 @@ import org.jabref.logic.util.StandardFileType; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; +import java.util.concurrent.Callable; + /** * Similar to {@link ExtractReferencesAction}. This action creates a new library, the other action "just" appends to the current library * @@ -54,26 +53,36 @@ 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. - stateManager.getActiveDatabase() - .flatMap(BibDatabaseContext::getDatabasePath) - .ifPresent(path -> builder.withInitialDirectory(path.getParent())); + // 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())); FileDialogConfiguration fileDialogConfiguration = builder.build(); LOGGER.trace("Opening file dialog with configuration: {}", fileDialogConfiguration); - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(path -> { - 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); - }); + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + path -> { + 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); + }); } protected abstract Callable getParserResultCallable(Path path); diff --git a/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java b/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java index e6b3180d4e74..9ddeb644d12c 100644 --- a/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java +++ b/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOffline.java @@ -1,8 +1,5 @@ package org.jabref.gui.maintable; -import java.nio.file.Path; -import java.util.concurrent.Callable; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; @@ -11,14 +8,23 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.TaskExecutor; +import java.nio.file.Path; +import java.util.concurrent.Callable; + 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(); } diff --git a/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java b/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java index 3b8c2fc0f5d3..7b8761b32f49 100644 --- a/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java +++ b/src/main/java/org/jabref/gui/maintable/NewLibraryFromPdfActionOnline.java @@ -1,8 +1,5 @@ package org.jabref.gui.maintable; -import java.nio.file.Path; -import java.util.concurrent.Callable; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; @@ -11,15 +8,25 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.TaskExecutor; +import java.nio.file.Path; +import java.util.concurrent.Callable; + 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/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java b/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java index 01265c3a1d07..66c1803bdb48 100644 --- a/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java +++ b/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java @@ -1,8 +1,5 @@ package org.jabref.gui.maintable; -import java.util.LinkedList; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -14,6 +11,9 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; +import java.util.LinkedList; +import java.util.List; + public class OpenExternalFileAction extends SimpleCommand { private final int FILES_LIMIT = 10; @@ -26,19 +26,21 @@ public class OpenExternalFileAction extends SimpleCommand { private final LinkedFile linkedFile; private final TaskExecutor taskExecutor; - public OpenExternalFileAction(DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - TaskExecutor taskExecutor) { + public OpenExternalFileAction( + DialogService dialogService, + StateManager stateManager, + GuiPreferences preferences, + TaskExecutor taskExecutor) { this(dialogService, stateManager, preferences, null, null, taskExecutor); } - public OpenExternalFileAction(DialogService dialogService, - StateManager stateManager, - GuiPreferences preferences, - BibEntry entry, - LinkedFile linkedFile, - TaskExecutor taskExecutor) { + public OpenExternalFileAction( + DialogService dialogService, + StateManager stateManager, + GuiPreferences preferences, + BibEntry entry, + LinkedFile linkedFile, + TaskExecutor taskExecutor) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; @@ -47,9 +49,9 @@ public OpenExternalFileAction(DialogService dialogService, this.taskExecutor = taskExecutor; if (this.linkedFile == null) { - this.executable.bind(ActionHelper.hasLinkedFileForSelectedEntries(stateManager) - .and(ActionHelper.needsEntriesSelected(stateManager)) - ); + this.executable.bind( + ActionHelper.hasLinkedFileForSelectedEntries(stateManager) + .and(ActionHelper.needsEntriesSelected(stateManager))); } else { this.setExecutable(true); } @@ -62,48 +64,61 @@ public OpenExternalFileAction(DialogService dialogService, */ @Override public void execute() { - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - if (entry == null) { - final List selectedEntries = stateManager.getSelectedEntries(); + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> { + if (entry == null) { + final List selectedEntries = + stateManager.getSelectedEntries(); - List linkedFileViewModelList = new LinkedList<>(); - LinkedFileViewModel linkedFileViewModel; + List linkedFileViewModelList = + new LinkedList<>(); + LinkedFileViewModel linkedFileViewModel; - for (BibEntry entry : selectedEntries) { - for (LinkedFile linkedFile : entry.getFiles()) { - linkedFileViewModel = new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); + for (BibEntry entry : selectedEntries) { + for (LinkedFile linkedFile : entry.getFiles()) { + linkedFileViewModel = + new LinkedFileViewModel( + linkedFile, + entry, + databaseContext, + taskExecutor, + dialogService, + preferences); - linkedFileViewModelList.add(linkedFileViewModel); - } - } + linkedFileViewModelList.add(linkedFileViewModel); + } + } - // ask the user when detecting # of files > FILES_LIMIT - 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("Continue"), Localization.lang("Cancel")); - if (!continueOpening) { - return; - } - } + // ask the user when detecting # of files > FILES_LIMIT + 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("Continue"), + Localization.lang("Cancel")); + if (!continueOpening) { + return; + } + } - linkedFileViewModelList.forEach(LinkedFileViewModel::open); - } else { - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); - linkedFileViewModel.open(); - } - }); + linkedFileViewModelList.forEach(LinkedFileViewModel::open); + } else { + LinkedFileViewModel linkedFileViewModel = + new LinkedFileViewModel( + linkedFile, + entry, + databaseContext, + taskExecutor, + dialogService, + preferences); + linkedFileViewModel.open(); + } + }); } } diff --git a/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java b/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java index 8d3e362405c4..c50b6762223f 100644 --- a/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java +++ b/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java @@ -20,19 +20,21 @@ public class OpenFolderAction extends SimpleCommand { 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; @@ -41,7 +43,8 @@ public OpenFolderAction(DialogService dialogService, this.taskExecutor = taskExecutor; if (this.linkedFile == null) { - this.executable.bind(ActionHelper.isFilePresentForSelectedEntry(stateManager, preferences)); + this.executable.bind( + ActionHelper.isFilePresentForSelectedEntry(stateManager, preferences)); } else { this.setExecutable(true); } @@ -49,28 +52,36 @@ public OpenFolderAction(DialogService dialogService, @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); - linkedFileViewModel.openFolder(); - }); - } else { - LinkedFileViewModel linkedFileViewModel = new LinkedFileViewModel( - linkedFile, - entry, - databaseContext, - taskExecutor, - dialogService, - preferences); - linkedFileViewModel.openFolder(); - } - }); + 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(); + } + }); } } diff --git a/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java b/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java index 48778884953a..9acef4d29cd5 100644 --- a/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java +++ b/src/main/java/org/jabref/gui/maintable/OpenUrlAction.java @@ -1,9 +1,5 @@ package org.jabref.gui.maintable; -import java.io.IOException; -import java.util.List; -import java.util.Optional; - import javafx.beans.binding.BooleanExpression; import org.jabref.gui.DialogService; @@ -17,67 +13,91 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + 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) { + public OpenUrlAction( + DialogService dialogService, StateManager stateManager, GuiPreferences preferences) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferences = preferences; - BooleanExpression fieldIsSet = ActionHelper.isAnyFieldSetForSelectedEntry( - List.of(StandardField.URL, StandardField.DOI, StandardField.URI, StandardField.EPRINT), - stateManager); + BooleanExpression fieldIsSet = + ActionHelper.isAnyFieldSetForSelectedEntry( + List.of( + StandardField.URL, + StandardField.DOI, + StandardField.URI, + StandardField.EPRINT), + stateManager); this.executable.bind(ActionHelper.needsEntriesSelected(1, stateManager).and(fieldIsSet)); } @Override public void execute() { - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - final List entries = stateManager.getSelectedEntries(); + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> { + final List entries = stateManager.getSelectedEntries(); - if (entries.size() != 1) { - dialogService.notify(Localization.lang("This operation requires exactly one item to be selected.")); - return; - } + if (entries.size() != 1) { + dialogService.notify( + Localization.lang( + "This operation requires exactly one item to be selected.")); + return; + } - BibEntry entry = entries.getFirst(); + BibEntry entry = entries.getFirst(); - // ToDo: Create dialog or menu to chose which one to open - // URL - DOI - DOI - EPRINT - Optional link = entry.getField(StandardField.EPRINT); - Field field = StandardField.EPRINT; - if (entry.hasField(StandardField.URI)) { - link = entry.getField(StandardField.URI); - field = StandardField.URI; - } - if (entry.hasField(StandardField.ISBN)) { - link = entry.getField(StandardField.ISBN); - field = StandardField.ISBN; - } - if (entry.hasField(StandardField.DOI)) { - link = entry.getField(StandardField.DOI); - field = StandardField.DOI; - } - if (entry.hasField(StandardField.URL)) { - link = entry.getField(StandardField.URL); - field = StandardField.URL; - } + // ToDo: Create dialog or menu to chose which one to open + // URL - DOI - DOI - EPRINT + Optional link = entry.getField(StandardField.EPRINT); + Field field = StandardField.EPRINT; + if (entry.hasField(StandardField.URI)) { + link = entry.getField(StandardField.URI); + field = StandardField.URI; + } + if (entry.hasField(StandardField.ISBN)) { + link = entry.getField(StandardField.ISBN); + field = StandardField.ISBN; + } + if (entry.hasField(StandardField.DOI)) { + link = entry.getField(StandardField.DOI); + field = StandardField.DOI; + } + if (entry.hasField(StandardField.URL)) { + link = entry.getField(StandardField.URL); + field = StandardField.URL; + } - if (link.isPresent()) { - 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) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); - } - } - }); + if (link.isPresent()) { + 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) { + dialogService.showErrorDialogAndWait( + Localization.lang("Unable to open link."), e); + } + } + }); } } diff --git a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java index d31f8574212d..acd38522db64 100644 --- a/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java +++ b/src/main/java/org/jabref/gui/maintable/PersistenceVisualStateTable.java @@ -1,8 +1,5 @@ package org.jabref.gui.maintable; -import java.util.List; -import java.util.stream.Collectors; - import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; import javafx.scene.control.TableColumn; @@ -10,10 +7,12 @@ import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.logic.preferences.JabRefCliPreferences; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.stream.Collectors; + /** * Keep track of changes made to the columns (reordering, resorting, resizing). */ @@ -24,24 +23,30 @@ public class PersistenceVisualStateTable { protected final TableView table; protected final ColumnPreferences preferences; - public PersistenceVisualStateTable(TableView table, ColumnPreferences preferences) { + public PersistenceVisualStateTable( + TableView table, ColumnPreferences preferences) { this.table = table; this.preferences = preferences; } 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, + // 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()); - }); + .map(col -> ((MainTableColumn) col).getModel()) + .forEach( + model -> { + model.widthProperty().addListener(obs -> updateColumns()); + model.sortTypeProperty().addListener(obs -> updateColumns()); + }); } /** @@ -66,11 +71,12 @@ private void updateSortOrder() { preferences.setColumnSortOrder(toList(table.getSortOrder())); } - private List toList(List> columns) { + 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/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 66430e8770e4..89e681390d92 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -1,6 +1,6 @@ package org.jabref.gui.maintable; -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; import javafx.scene.control.ContextMenu; import javafx.scene.control.Menu; @@ -35,123 +35,350 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.SpecialField; -import com.tobiasdiez.easybind.EasyBind; +import javax.swing.undo.UndoManager; 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) { + 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) { 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), - 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 OpenExternalFileAction(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(), + 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); - new ChangeEntryTypeMenu(libraryTab.getSelectedEntries(), libraryTab.getBibDatabaseContext(), undoManager, entryTypesManager).asSubMenu(), - factory.createMenuItem(StandardActions.MERGE_WITH_FETCHED_ENTRY, new MergeWithFetchedEntryAction(dialogService, stateManager, taskExecutor, preferences, undoManager)) - ); + contextMenu + .getItems() + .addAll( + factory.createMenuItem( + StandardActions.COPY, + new EditAction( + StandardActions.COPY, + () -> libraryTab, + stateManager, + undoManager)), + createCopySubMenu( + factory, + dialogService, + stateManager, + preferences, + clipBoardManager, + abbreviationRepository, + taskExecutor), + 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 OpenExternalFileAction( + 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))); - EasyBind.subscribe(preferences.getGrobidPreferences().grobidEnabledProperty(), enabled -> { - extractFileReferencesOnline.setVisible(enabled); - extractFileReferencesOffline.setVisible(!enabled); - }); + EasyBind.subscribe( + preferences.getGrobidPreferences().grobidEnabledProperty(), + enabled -> { + extractFileReferencesOnline.setVisible(enabled); + extractFileReferencesOffline.setVisible(!enabled); + }); return contextMenu; } - 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() - ); + 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))); + 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))); } else { - copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new CopyCitationAction(CitationStyleOutputFormat.HTML, dialogService, stateManager, clipBoardManager, taskExecutor, preferences, abbreviationRepository))); + 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/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java b/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java index 4b6a251c4ffc..622f5c189943 100644 --- a/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java +++ b/src/main/java/org/jabref/gui/maintable/SearchShortScienceAction.java @@ -1,7 +1,7 @@ package org.jabref.gui.maintable; -import java.io.IOException; -import java.util.List; +import static org.jabref.gui.actions.ActionHelper.isFieldSetForSelectedEntry; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; import javafx.beans.binding.BooleanExpression; @@ -15,39 +15,57 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; -import static org.jabref.gui.actions.ActionHelper.isFieldSetForSelectedEntry; -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; +import java.io.IOException; +import java.util.List; 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; - BooleanExpression fieldIsSet = isFieldSetForSelectedEntry(StandardField.TITLE, stateManager); + BooleanExpression fieldIsSet = + isFieldSetForSelectedEntry(StandardField.TITLE, stateManager); this.executable.bind(needsEntriesSelected(1, stateManager).and(fieldIsSet)); } @Override public void execute() { - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - final List bibEntries = stateManager.getSelectedEntries(); - - if (bibEntries.size() != 1) { - dialogService.notify(Localization.lang("This operation requires exactly one item to be selected.")); - return; - } - ExternalLinkCreator.getShortScienceSearchURL(bibEntries.getFirst()).ifPresent(url -> { - try { - NativeDesktop.openExternalViewer(databaseContext, preferences, url, StandardField.URL, dialogService, bibEntries.getFirst()); - } catch (IOException ex) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open ShortScience."), ex); - } - }); - }); + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> { + final List bibEntries = stateManager.getSelectedEntries(); + + if (bibEntries.size() != 1) { + dialogService.notify( + Localization.lang( + "This operation requires exactly one item to be selected.")); + return; + } + ExternalLinkCreator.getShortScienceSearchURL(bibEntries.getFirst()) + .ifPresent( + url -> { + try { + 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/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java index 4096455c7658..047d77719071 100644 --- a/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java +++ b/src/main/java/org/jabref/gui/maintable/SmartConstrainedResizePolicy.java @@ -1,10 +1,5 @@ package org.jabref.gui.maintable; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; - import javafx.scene.control.ResizeFeaturesBase; import javafx.scene.control.TableColumnBase; import javafx.scene.control.TableView; @@ -13,6 +8,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + /** * 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. @@ -20,7 +20,8 @@ */ public class SmartConstrainedResizePolicy implements Callback { - private static final Logger LOGGER = LoggerFactory.getLogger(SmartConstrainedResizePolicy.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(SmartConstrainedResizePolicy.class); @Override public Boolean call(TableView.ResizeFeatures prop) { @@ -34,17 +35,20 @@ public Boolean call(TableView.ResizeFeatures prop) { private Boolean initColumnSize(TableView table) { double tableWidth = getContentWidth(table); List> visibleLeafColumns = table.getVisibleLeafColumns(); - double totalWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); + double totalWidth = + visibleLeafColumns.stream().mapToDouble(TableColumnBase::getWidth).sum(); if (Math.abs(totalWidth - tableWidth) > 1) { - double totalPrefWidth = visibleLeafColumns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); + double totalPrefWidth = + visibleLeafColumns.stream().mapToDouble(TableColumnBase::getPrefWidth).sum(); double currPrefWidth = 0; if (totalPrefWidth > 0) { for (TableColumnBase col : visibleLeafColumns) { 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; @@ -64,10 +68,14 @@ private void resize(TableColumnBase column, double delta) { try { // TODO: reflective access, should be removed Class clazz = Class.forName("javafx.scene.control.TableUtil"); - Method constrainedResize = clazz.getDeclaredMethod("resize", TableColumnBase.class, double.class); + 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); } } @@ -75,22 +83,34 @@ 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); + 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; } diff --git a/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java b/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java index e272467f409d..23aa3da3c245 100644 --- a/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/FieldColumn.java @@ -1,5 +1,7 @@ package org.jabref.gui.maintable.columns; +import com.google.common.collect.MoreCollectors; + import javafx.beans.value.ObservableValue; import javafx.scene.control.Tooltip; @@ -13,8 +15,6 @@ import org.jabref.model.entry.field.OrFields; import org.jabref.model.entry.field.UnknownField; -import com.google.common.collect.MoreCollectors; - /** * A column that displays the text-value of the field */ diff --git a/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java b/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java index ce1fc758ef30..73eafbf5e818 100644 --- a/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/FileColumn.java @@ -1,9 +1,5 @@ package org.jabref.gui.maintable.columns; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - import javafx.scene.Node; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; @@ -26,6 +22,10 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.LinkedFile; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + /** * A column that draws a clickable symbol for either all the files of a defined file type * or a joined column with all the files of any type @@ -40,11 +40,12 @@ public class FileColumn extends MainTableColumn> { /** * 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; @@ -61,30 +62,36 @@ public FileColumn(MainTableColumnModel model, .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(); - } - }) + .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,13 +100,25 @@ 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()))) + .withGraphic( + (entry, linkedFiles) -> + createFileIcon( + entry, + linkedFiles.stream() + .filter( + linkedFile -> + linkedFile + .getFileType() + .equalsIgnoreCase(fileType)) + .collect(Collectors.toList()))) .install(this); } @@ -125,15 +144,19 @@ private ContextMenu createFileMenu(BibEntryTableViewModel entry, List linkedFileViewModel.open()); contextMenu.getItems().add(menuItem); } @@ -148,10 +171,13 @@ private Node createFileIcon(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(); + return ExternalFileTypes.getExternalFileTypeByLinkedFile( + linkedFiles.getFirst(), + true, + preferences.getExternalApplicationsPreferences()) + .map(ExternalFileType::getIcon) + .orElse(IconTheme.JabRefIcons.FILE) + .getGraphicNode(); } else { return null; } diff --git a/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java b/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java index a02359c42b91..266a01a4df2c 100644 --- a/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/LibraryColumn.java @@ -13,8 +13,9 @@ 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()); } diff --git a/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java b/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java index 22aa68e43f43..eda8c69224ce 100644 --- a/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/LinkedIdentifierColumn.java @@ -1,8 +1,5 @@ package org.jabref.gui.maintable.columns; -import java.io.IOException; -import java.util.Map; - import javafx.scene.Node; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; @@ -26,6 +23,9 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.field.Field; +import java.io.IOException; +import java.util.Map; + /** * A clickable icons column for DOIs, URLs, URIs and EPrints. */ @@ -36,12 +36,13 @@ public class LinkedIdentifierColumn extends MainTableColumn> 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; @@ -59,12 +60,16 @@ public LinkedIdentifierColumn(MainTableColumnModel model, .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(); - } - }) + .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); } @@ -80,31 +85,56 @@ private Node createIdentifierGraphic(Map values) { 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(); } - private ContextMenu createIdentifierMenu(BibEntryTableViewModel entry, Map values) { + private ContextMenu createIdentifierMenu( + BibEntryTableViewModel entry, Map values) { ContextMenu contextMenu = new ContextMenu(); if (values.size() <= 1) { return null; } - values.keySet().forEach(field -> { - 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) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to open link."), e); - } - event.consume(); - }); - contextMenu.getItems().add(menuItem); - }); + values.keySet() + .forEach( + field -> { + 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) { + dialogService.showErrorDialogAndWait( + Localization.lang("Unable to open link."), e); + } + event.consume(); + }); + contextMenu.getItems().add(menuItem); + }); return contextMenu; } diff --git a/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java b/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java index 27266e8f8e57..770ec0b0ed20 100644 --- a/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java +++ b/src/main/java/org/jabref/gui/maintable/columns/SpecialFieldColumn.java @@ -1,8 +1,6 @@ package org.jabref.gui.maintable.columns; -import java.util.Optional; - -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; import javafx.scene.Node; import javafx.scene.control.ContextMenu; @@ -11,6 +9,7 @@ import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; +import org.controlsfx.control.Rating; import org.jabref.gui.icon.JabRefIcon; import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.ColumnPreferences; @@ -28,8 +27,9 @@ import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.SpecialFieldValue; -import com.tobiasdiez.easybind.EasyBind; -import org.controlsfx.control.Rating; +import java.util.Optional; + +import javax.swing.undo.UndoManager; /** * A column that displays a SpecialField @@ -39,13 +39,15 @@ public class SpecialFieldColumn extends MainTableColumn() .withGraphic(this::createSpecialRating) @@ -63,18 +66,29 @@ public SpecialFieldColumn(MainTableColumnModel model, CliPreferences preferences 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()); - } - }) + new OptionalValueTableCellFactory< + BibEntryTableViewModel, SpecialFieldValueViewModel>() + .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)) + new OptionalValueTableCellFactory< + BibEntryTableViewModel, SpecialFieldValueViewModel>() + .withGraphic( + (entry, value) -> + createSpecialFieldIcon(value, specialFieldViewModel)) + .withMenu( + (entry, value) -> + createSpecialFieldMenu( + entry.getEntry(), specialFieldViewModel)) .install(this); } } @@ -90,7 +104,8 @@ public SpecialFieldColumn(MainTableColumnModel model, CliPreferences preferences this.setSortable(true); } - private Rating createSpecialRating(BibEntryTableViewModel entry, Optional value) { + private Rating createSpecialRating( + BibEntryTableViewModel entry, Optional value) { Rating ranking = new Rating(); if (value.isPresent()) { @@ -99,18 +114,24 @@ private Rating createSpecialRating(BibEntryTableViewModel entry, Optional { - if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) { - ranking.setRating(0); - event.consume(); - } else if (event.getButton() == MouseButton.SECONDARY) { - event.consume(); - } - }); - - EasyBind.subscribe(ranking.ratingProperty(), rating -> - new SpecialFieldViewModel(SpecialField.RANKING, preferences, undoManager) - .setSpecialFieldValue(entry.getEntry(), SpecialFieldValue.getRating(rating.intValue()))); + ranking.addEventFilter( + MouseEvent.MOUSE_CLICKED, + event -> { + if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) { + ranking.setRating(0); + event.consume(); + } else if (event.getButton() == MouseButton.SECONDARY) { + event.consume(); + } + }); + + EasyBind.subscribe( + ranking.ratingProperty(), + rating -> + new SpecialFieldViewModel(SpecialField.RANKING, preferences, undoManager) + .setSpecialFieldValue( + entry.getEntry(), + SpecialFieldValue.getRating(rating.intValue()))); return ranking; } @@ -119,21 +140,28 @@ 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.setOnAction(event -> specialField.setSpecialFieldValue(entry, value.getValue())); + 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); } 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/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java index 8d7a70f3e9e8..0dd1e26a9222 100644 --- a/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java +++ b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java @@ -1,9 +1,5 @@ package org.jabref.gui.menus; -import java.util.List; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableChangeType; @@ -11,6 +7,10 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.types.EntryType; +import java.util.List; + +import javax.swing.undo.UndoManager; + public class ChangeEntryTypeAction extends SimpleCommand { private final EntryType type; @@ -26,8 +26,12 @@ 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/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java index 4ad7dbe3cb27..15f51fab6ec5 100644 --- a/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java +++ b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java @@ -1,11 +1,5 @@ package org.jabref.gui.menus; -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.ContextMenu; @@ -23,6 +17,12 @@ import org.jabref.model.entry.types.BibtexEntryTypeDefinitions; import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + public class ChangeEntryTypeMenu { private final List entries; @@ -31,10 +31,11 @@ public class ChangeEntryTypeMenu { 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,43 +55,57 @@ 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()) { // Default BibLaTeX - items.addAll(fromEntryTypes(entryTypesManager.getAllTypes(BibDatabaseMode.BIBLATEX), entries, undoManager)); + 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 - )); + 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) + 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 +116,15 @@ 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/src/main/java/org/jabref/gui/mergeentries/DiffHighlighting.java b/src/main/java/org/jabref/gui/mergeentries/DiffHighlighting.java index 06c6c0cd1f77..35de4247341e 100644 --- a/src/main/java/org/jabref/gui/mergeentries/DiffHighlighting.java +++ b/src/main/java/org/jabref/gui/mergeentries/DiffHighlighting.java @@ -1,24 +1,28 @@ package org.jabref.gui.mergeentries; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import com.github.difflib.DiffUtils; +import com.github.difflib.patch.AbstractDelta; import javafx.scene.text.Text; -import com.github.difflib.DiffUtils; -import com.github.difflib.patch.AbstractDelta; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; public class DiffHighlighting { - private DiffHighlighting() { - } + private DiffHighlighting() {} - public static List generateDiffHighlighting(String baseString, String modifiedString, String separator) { + public static List generateDiffHighlighting( + String baseString, String modifiedString, String separator) { 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> deltaList = + DiffUtils.diff(baseStringSplit, modifiedStringSplit).getDeltas(); + 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,8 +33,12 @@ 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.add(startPos + offset, forAdded(String.join(separator, delta.getTarget().getLines()))); + 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: for (String line : lines) { @@ -39,7 +47,9 @@ 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; diff --git a/src/main/java/org/jabref/gui/mergeentries/DiffHighlightingEllipsingTextFlow.java b/src/main/java/org/jabref/gui/mergeentries/DiffHighlightingEllipsingTextFlow.java index 50970efb814c..44d2b86b4dfa 100644 --- a/src/main/java/org/jabref/gui/mergeentries/DiffHighlightingEllipsingTextFlow.java +++ b/src/main/java/org/jabref/gui/mergeentries/DiffHighlightingEllipsingTextFlow.java @@ -1,6 +1,6 @@ package org.jabref.gui.mergeentries; -import java.util.List; +import com.tobiasdiez.easybind.EasyObservableValue; import javafx.beans.DefaultProperty; import javafx.beans.property.ObjectProperty; @@ -13,23 +13,27 @@ import javafx.scene.text.Text; import javafx.scene.text.TextFlow; -import com.tobiasdiez.easybind.EasyObservableValue; +import java.util.List; @DefaultProperty("children") public class DiffHighlightingEllipsingTextFlow extends TextFlow { - private final static String DEFAULT_ELLIPSIS_STRING = "..."; + private static final String DEFAULT_ELLIPSIS_STRING = "..."; private StringProperty ellipsisString; private final ObservableList allChildren = FXCollections.observableArrayList(); - private final ChangeListener sizeChangeListener = (observableValue, number, t1) -> adjustText(); + 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); @@ -89,7 +93,8 @@ private boolean fillUntilOverflowing() { // all Texts are displayed, let's make sure all chars are as well Node lastChildAsShown = super.getChildren().get(super.getChildren().size() - 1); 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 { // nothing to fill the space with @@ -106,12 +111,15 @@ private boolean fillUntilOverflowing() { private boolean ellipseUntilTextFits() { while (getHeight() > getMaxHeight() || getWidth() > getMaxWidth()) { - Text lastChildAsShown = (Text) super.getChildren().remove(super.getChildren().size() - 1); - while (getEllipsisString().equals(lastChildAsShown.getText()) || "".equals(lastChildAsShown.getText())) { + Text lastChildAsShown = + (Text) super.getChildren().remove(super.getChildren().size() - 1); + while (getEllipsisString().equals(lastChildAsShown.getText()) + || "".equals(lastChildAsShown.getText())) { if (super.getChildren().isEmpty()) { return false; } - lastChildAsShown = (Text) super.getChildren().remove(super.getChildren().size() - 1); + lastChildAsShown = + (Text) super.getChildren().remove(super.getChildren().size() - 1); } Text shortenedChild = new Text(ellipseString(lastChildAsShown.getText())); shortenedChild.getStyleClass().addAll(lastChildAsShown.getStyleClass()); @@ -124,16 +132,23 @@ private boolean ellipseUntilTextFits() { public void highlightDiff() { allChildren.clear(); if (comparisonString.get() != null && !comparisonString.get().equals(fullText)) { - final List highlightedText = switch (diffMode.getValue()) { - case PLAIN -> { - Text text = new Text(fullText); - text.getStyleClass().add("text-unchanged"); - yield List.of(text); - } - case WORD -> DiffHighlighting.generateDiffHighlighting(comparisonString.get(), fullText, " "); - case CHARACTER -> DiffHighlighting.generateDiffHighlighting(comparisonString.get(), fullText, ""); - default -> throw new UnsupportedOperationException("Not implemented " + diffMode.getValue()); - }; + final List highlightedText = + switch (diffMode.getValue()) { + case PLAIN -> { + Text text = new Text(fullText); + text.getStyleClass().add("text-unchanged"); + yield List.of(text); + } + case WORD -> + DiffHighlighting.generateDiffHighlighting( + comparisonString.get(), fullText, " "); + case CHARACTER -> + DiffHighlighting.generateDiffHighlighting( + comparisonString.get(), fullText, ""); + default -> + throw new UnsupportedOperationException( + "Not implemented " + diffMode.getValue()); + }; allChildren.addAll(highlightedText); } else { Text text = new Text(fullText); diff --git a/src/main/java/org/jabref/gui/mergeentries/DiffMode.java b/src/main/java/org/jabref/gui/mergeentries/DiffMode.java index de05daebb10e..b46f0b4f4b1b 100644 --- a/src/main/java/org/jabref/gui/mergeentries/DiffMode.java +++ b/src/main/java/org/jabref/gui/mergeentries/DiffMode.java @@ -3,7 +3,6 @@ import org.jabref.logic.l10n.Localization; public enum DiffMode { - PLAIN(Localization.lang("None")), WORD(Localization.lang("Word by word")), CHARACTER(Localization.lang("Character by character")), diff --git a/src/main/java/org/jabref/gui/mergeentries/EntriesMergeResult.java b/src/main/java/org/jabref/gui/mergeentries/EntriesMergeResult.java index b134b11688a0..737b20eaed1e 100644 --- a/src/main/java/org/jabref/gui/mergeentries/EntriesMergeResult.java +++ b/src/main/java/org/jabref/gui/mergeentries/EntriesMergeResult.java @@ -3,6 +3,8 @@ import org.jabref.model.entry.BibEntry; public record EntriesMergeResult( - BibEntry originalLeftEntry, BibEntry originalRightEntry, BibEntry newLeftEntry, BibEntry newRightEntry, BibEntry mergedEntry -) { -} + BibEntry originalLeftEntry, + BibEntry originalRightEntry, + BibEntry newLeftEntry, + BibEntry newRightEntry, + BibEntry mergedEntry) {} diff --git a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java index dbe205a24c48..8b3ec6b80423 100644 --- a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java +++ b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java @@ -1,15 +1,5 @@ package org.jabref.gui.mergeentries; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.NamedCompound; @@ -31,16 +21,26 @@ import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.EntryType; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; + +import javax.swing.undo.UndoManager; + /** * Class for fetching and merging bibliographic information */ public class FetchAndMergeEntry { - public static List SUPPORTED_FIELDS = Arrays.asList(StandardField.DOI, StandardField.EPRINT, StandardField.ISBN); + 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; @@ -49,11 +49,12 @@ public class FetchAndMergeEntry { 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; @@ -73,30 +74,58 @@ 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()); + Optional fetcher = + WebFetchers.getIdBasedFetcherForField( + field, preferences.getImportFormatPreferences()); if (fetcher.isPresent()) { BackgroundTask.wrap(() -> fetcher.get().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(), fetcher.get()); - } 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", fetcher.get().getName()), Localization.lang("No data was found for the identifier")); - } else if (exception instanceof FetcherServerException) { - dialogService.showInformationDialogAndWait(Localization.lang("Fetching information using %0", fetcher.get().getName()), Localization.lang("Server not available")); - } else { - dialogService.showInformationDialogAndWait(Localization.lang("Fetching information using %0", fetcher.get().getName()), Localization.lang("Error occurred %0", exception.getMessage())); - } - }) - .executeWith(taskExecutor); + .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(), fetcher.get()); + } 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", + fetcher.get().getName()), + Localization.lang( + "No data was found for the identifier")); + } else if (exception instanceof FetcherServerException) { + dialogService.showInformationDialogAndWait( + Localization.lang( + "Fetching information using %0", + fetcher.get().getName()), + Localization.lang("Server not available")); + } else { + dialogService.showInformationDialogAndWait( + Localization.lang( + "Fetching information using %0", + fetcher.get().getName()), + Localization.lang( + "Error occurred %0", + exception.getMessage())); + } + }) + .executeWith(taskExecutor); } } else { dialogService.notify(Localization.lang("No %0 found", field.getDisplayName())); @@ -104,15 +133,21 @@ public void fetchAndMerge(BibEntry entry, List fields) { } } - private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebFetcher fetcher) { - MergeEntriesDialog dialog = new MergeEntriesDialog(originalEntry, fetchedEntry, preferences); + private void showMergeDialog( + BibEntry originalEntry, BibEntry fetchedEntry, WebFetcher fetcher) { + MergeEntriesDialog dialog = + new MergeEntriesDialog(originalEntry, fetchedEntry, preferences); dialog.setTitle(Localization.lang("Merge entry with %0 information", fetcher.getName())); dialog.setLeftHeaderText(Localization.lang("Original entry")); dialog.setRightHeaderText(Localization.lang("Entry from %0", fetcher.getName())); - 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)); @@ -136,9 +171,14 @@ 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 - ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.orElse(null), - mergedString.get())); + originalEntry.setField( + field, mergedString.get()); // mergedString always present + ce.addEdit( + new UndoableFieldChange( + originalEntry, + field, + originalString.orElse(null), + mergedString.get())); edited = true; } } @@ -148,7 +188,12 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF 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; } } @@ -156,7 +201,8 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF if (edited) { ce.end(); undoManager.addEdit(ce); - dialogService.notify(Localization.lang("Updated entry with info from %0", fetcher.getName())); + dialogService.notify( + Localization.lang("Updated entry with info from %0", fetcher.getName())); } else { dialogService.notify(Localization.lang("No information added")); } @@ -167,19 +213,32 @@ private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebF 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); + .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/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java b/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java index 15709455d69c..c5432ed7825b 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeDialogPreferences.java @@ -14,15 +14,17 @@ public class MergeDialogPreferences { 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) { + private final ObjectProperty + 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); @@ -30,7 +32,8 @@ public MergeDialogPreferences(DiffMode mergeDiffMode, this.mergeShowChangedFieldsOnly = new SimpleBooleanProperty(mergeShowChangedFieldsOnly); this.mergeApplyToAllEntries = new SimpleBooleanProperty(mergeApplyToAllEntries); - this.allEntriesDuplicateResolverDecision = new SimpleObjectProperty<>(allEntriesDuplicateResolverDecision); + this.allEntriesDuplicateResolverDecision = + new SimpleObjectProperty<>(allEntriesDuplicateResolverDecision); } public DiffMode getMergeDiffMode() { @@ -105,15 +108,18 @@ 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); } - public DuplicateResolverDialog.DuplicateResolverResult getAllEntriesDuplicateResolverDecision() { + public DuplicateResolverDialog.DuplicateResolverResult + getAllEntriesDuplicateResolverDecision() { return allEntriesDuplicateResolverDecision.get(); } - public ObjectProperty allEntriesDuplicateResolverDecisionProperty() { + public ObjectProperty + allEntriesDuplicateResolverDecisionProperty() { return allEntriesDuplicateResolverDecision; } } diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java index 82c7b07c95f5..aca832b8db0f 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java @@ -1,10 +1,5 @@ package org.jabref.gui.mergeentries; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -15,6 +10,11 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.InternalField; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + public class MergeEntriesAction extends SimpleCommand { private static final int NUMBER_OF_ENTRIES_NEEDED = 2; private final DialogService dialogService; @@ -22,13 +22,18 @@ public class MergeEntriesAction extends SimpleCommand { 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; this.preferences = preferences; - this.executable.bind(ActionHelper.needsEntriesSelected(NUMBER_OF_ENTRIES_NEEDED, stateManager)); + this.executable.bind( + ActionHelper.needsEntriesSelected(NUMBER_OF_ENTRIES_NEEDED, stateManager)); } @Override @@ -54,7 +59,8 @@ public void execute() { // compare two entries BibEntry first; BibEntry second; - EntryComparator entryComparator = new EntryComparator(false, false, InternalField.KEY_FIELD); + EntryComparator entryComparator = + new EntryComparator(false, false, InternalField.KEY_FIELD); if (entryComparator.compare(one, two) <= 0) { first = one; second = two; @@ -67,10 +73,13 @@ public void execute() { dialog.setTitle(Localization.lang("Merge entries")); Optional mergeResultOpt = dialogService.showCustomDialogAndWait(dialog); - mergeResultOpt.ifPresentOrElse(entriesMergeResult -> { - new MergeTwoEntriesAction(entriesMergeResult, stateManager, undoManager).execute(); + mergeResultOpt.ifPresentOrElse( + entriesMergeResult -> { + new MergeTwoEntriesAction(entriesMergeResult, stateManager, undoManager) + .execute(); - dialogService.notify(Localization.lang("Merged entries")); - }, () -> dialogService.notify(Localization.lang("Canceled merging entries"))); + dialogService.notify(Localization.lang("Merged entries")); + }, + () -> dialogService.notify(Localization.lang("Canceled merging entries"))); } } diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java index 9613b457991b..81f415b671ba 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java @@ -33,16 +33,23 @@ private void init() { this.getDialogPane().setContent(threeWayMergeView); // Create buttons - ButtonType replaceEntries = new ButtonType(Localization.lang("Merge entries"), ButtonBar.ButtonData.OK_DONE); + ButtonType replaceEntries = + new ButtonType(Localization.lang("Merge entries"), ButtonBar.ButtonData.OK_DONE); this.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, replaceEntries); - this.setResultConverter(buttonType -> { - threeWayMergeView.saveConfiguration(); - if (buttonType.equals(replaceEntries)) { - return new EntriesMergeResult(one, two, threeWayMergeView.getLeftEntry(), threeWayMergeView.getRightEntry(), threeWayMergeView.getMergedEntry()); - } else { - return null; - } - }); + this.setResultConverter( + buttonType -> { + threeWayMergeView.saveConfiguration(); + if (buttonType.equals(replaceEntries)) { + return new EntriesMergeResult( + one, + two, + threeWayMergeView.getLeftEntry(), + threeWayMergeView.getRightEntry(), + threeWayMergeView.getMergedEntry()); + } else { + return null; + } + }); } public void setLeftHeaderText(String leftHeaderText) { diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeResult.java b/src/main/java/org/jabref/gui/mergeentries/MergeResult.java index 6db5a7a60e5a..a91af4d0083f 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeResult.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeResult.java @@ -2,7 +2,4 @@ import org.jabref.model.entry.BibEntry; -public record MergeResult( - BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry -) { -} +public record MergeResult(BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry) {} diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeTwoEntriesAction.java b/src/main/java/org/jabref/gui/mergeentries/MergeTwoEntriesAction.java index 7ece7d1c8f5d..47f7fe668b6a 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeTwoEntriesAction.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeTwoEntriesAction.java @@ -1,10 +1,5 @@ package org.jabref.gui.mergeentries; -import java.util.Arrays; -import java.util.List; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.undo.NamedCompound; @@ -14,12 +9,20 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; +import java.util.Arrays; +import java.util.List; + +import javax.swing.undo.UndoManager; + 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,13 +35,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(); diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java b/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java index 743b0aac9005..1b993c0f2dbf 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeWithFetchedEntryAction.java @@ -1,7 +1,5 @@ package org.jabref.gui.mergeentries; -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -13,6 +11,8 @@ import org.jabref.model.entry.field.OrFields; import org.jabref.model.entry.field.StandardField; +import javax.swing.undo.UndoManager; + public class MergeWithFetchedEntryAction extends SimpleCommand { private final DialogService dialogService; @@ -21,19 +21,23 @@ public class MergeWithFetchedEntryAction extends SimpleCommand { 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; this.preferences = preferences; this.undoManager = undoManager; - this.executable.bind(ActionHelper.needsEntriesSelected(1, stateManager) - .and(ActionHelper.isAnyFieldSetForSelectedEntry(FetchAndMergeEntry.SUPPORTED_FIELDS, stateManager))); + this.executable.bind( + ActionHelper.needsEntriesSelected(1, stateManager) + .and( + ActionHelper.isAnyFieldSetForSelectedEntry( + FetchAndMergeEntry.SUPPORTED_FIELDS, stateManager))); } @Override @@ -44,11 +48,23 @@ 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( + "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/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesView.java b/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesView.java index 1dfebf65705c..1fdf73ac84f6 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesView.java +++ b/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesView.java @@ -1,10 +1,8 @@ package org.jabref.gui.mergeentries; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.function.Supplier; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -49,13 +47,15 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; - -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.EasyObservableValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + public class MultiMergeEntriesView extends BaseDialog { private static final Logger LOGGER = LoggerFactory.getLogger(MultiMergeEntriesView.class); @@ -85,39 +85,55 @@ public class MultiMergeEntriesView extends BaseDialog { 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); + ButtonType mergeEntries = + new ButtonType(Localization.lang("Merge entries"), ButtonBar.ButtonData.OK_DONE); this.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, mergeEntries); this.setResultConverter(viewModel::resultConverter); - viewModel.entriesProperty().addListener((ListChangeListener) c -> { - while (c.next()) { - if (c.wasAdded()) { - for (MultiMergeEntriesViewModel.EntrySource entrySourceColumn : c.getAddedSubList()) { - addColumn(entrySourceColumn); - } - } - } - }); - - 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 + .entriesProperty() + .addListener( + (ListChangeListener) + c -> { + while (c.next()) { + if (c.wasAdded()) { + for (MultiMergeEntriesViewModel.EntrySource + entrySourceColumn : c.getAddedSubList()) { + addColumn(entrySourceColumn); + } + } + } + }); + + 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 @@ -126,26 +142,34 @@ public void initialize() { leftScrollPane.vvalueProperty().bindBidirectional(centerScrollPane.vvalueProperty()); rightScrollPane.vvalueProperty().bindBidirectional(centerScrollPane.vvalueProperty()); - viewModel.failedSuppliersProperty().addListener((obs, oldValue, newValue) -> - failedSuppliers.setText(viewModel.failedSuppliersProperty().get().isEmpty() ? "" : Localization.lang( - "Could not extract Metadata from: %0", - String.join(", ", viewModel.failedSuppliersProperty()) - )) - ); + viewModel + .failedSuppliersProperty() + .addListener( + (obs, oldValue, newValue) -> + failedSuppliers.setText( + viewModel.failedSuppliersProperty().get().isEmpty() + ? "" + : Localization.lang( + "Could not extract Metadata from: %0", + String.join( + ", ", + viewModel + .failedSuppliersProperty())))); fillDiffModes(); } private void fillDiffModes() { - diffMode.setItems(FXCollections.observableList(List.of( - DiffMode.PLAIN, - DiffMode.WORD, - DiffMode.CHARACTER))); + 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) { @@ -168,16 +192,20 @@ private void addColumn(MultiMergeEntriesViewModel.EntrySource entrySourceColumn) writeBibEntryToColumn(entrySourceColumn, columnIndex); } else { header.setDisable(true); - entrySourceColumn.isLoadingProperty().addListener((observable, oldValue, newValue) -> { - if (!newValue && entrySourceColumn.entryProperty().get() != null) { - writeBibEntryToColumn(entrySourceColumn, columnIndex); - header.setDisable(false); - } - }); + entrySourceColumn + .isLoadingProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (!newValue && entrySourceColumn.entryProperty().get() != null) { + writeBibEntryToColumn(entrySourceColumn, columnIndex); + header.setDisable(false); + } + }); } } - private ToggleButton generateEntryHeader(MultiMergeEntriesViewModel.EntrySource column, int columnIndex) { + private ToggleButton generateEntryHeader( + MultiMergeEntriesViewModel.EntrySource column, int columnIndex) { ToggleButton header = new ToggleButton(); header.setToggleGroup(headerToggleGroup); header.textProperty().bind(column.titleProperty()); @@ -192,16 +220,18 @@ private ToggleButton generateEntryHeader(MultiMergeEntriesViewModel.EntrySource progressIndicator.visibleProperty().bind(column.isLoadingProperty()); } - column.isLoadingProperty().addListener((obs, oldValue, newValue) -> { - if (!newValue) { - header.setGraphic(null); - if (column.entryProperty().get() == null) { - header.setMinWidth(0); - header.setMaxWidth(0); - header.setVisible(false); - } - } - }); + column.isLoadingProperty() + .addListener( + (obs, oldValue, newValue) -> { + if (!newValue) { + header.setGraphic(null); + if (column.entryProperty().get() == null) { + header.setMinWidth(0); + header.setMaxWidth(0); + header.setVisible(false); + } + } + }); return header; } @@ -212,8 +242,10 @@ private ToggleButton generateEntryHeader(MultiMergeEntriesViewModel.EntrySource * @param entrySourceColumn the entry to write * @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()) { + private void writeBibEntryToColumn( + MultiMergeEntriesViewModel.EntrySource entrySourceColumn, int columnIndex) { + for (Map.Entry entry : + entrySourceColumn.entryProperty().get().getFieldsObservable().entrySet()) { Field key = entry.getKey(); String value = entry.getValue(); Cell cell = new Cell(value, key, columnIndex); @@ -229,17 +261,30 @@ private void writeBibEntryToColumn(MultiMergeEntriesViewModel.EntrySource entryS * @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))); - sourceButton.setSelected(true); - } - }); + 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))); + sourceButton.setSelected(true); + } + }); } /** @@ -252,7 +297,8 @@ private boolean isMultilineField(Field field) { if (field.equals(StandardField.DOI)) { return false; } - return FieldFactory.isMultiLineField(field, preferences.getFieldPreferences().getNonWrappableFields()); + return FieldFactory.isMultiLineField( + field, preferences.getFieldPreferences().getNonWrappableFields()); } private class Cell extends HBox { @@ -266,71 +312,96 @@ public Cell(String content, Field field, int columnIndex) { 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(() -> { - - FieldRow row = fieldRows.get(field); - - prefWidthProperty().bind(((Region) supplierHeader.getChildren().get(columnIndex)).widthProperty()); - setMinWidth(Control.USE_PREF_SIZE); - setMaxWidth(Control.USE_PREF_SIZE); - prefHeightProperty().bind(((Region) fieldEditor.getChildren().get(row.rowIndex)).heightProperty()); - setMinHeight(Control.USE_PREF_SIZE); - setMaxHeight(Control.USE_PREF_SIZE); - - // Button - ToggleButton cellButton = new ToggleButton(); - cellButton.prefHeightProperty().bind(heightProperty()); - cellButton.setMinHeight(Control.USE_PREF_SIZE); - cellButton.setMaxHeight(Control.USE_PREF_SIZE); - cellButton.setGraphicTextGap(0); - getChildren().add(cellButton); - cellButton.maxWidthProperty().bind(widthProperty()); - HBox.setHgrow(cellButton, Priority.ALWAYS); - - // Text - DiffHighlightingEllipsingTextFlow buttonText = new DiffHighlightingEllipsingTextFlow(content, viewModel.mergedEntryProperty().get().getFieldBinding(field).asOrdinary(), diffMode.valueProperty()); - - buttonText.maxWidthProperty().bind(widthProperty().add(-10)); - buttonText.maxHeightProperty().bind(heightProperty()); - cellButton.setGraphic(buttonText); - cellButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); - cellButton.setContentDisplay(ContentDisplay.CENTER); - - // Tooltip - Tooltip buttonTooltip = new Tooltip(content); - buttonTooltip.setWrapText(true); - buttonTooltip.prefWidthProperty().bind(widthProperty()); - buttonTooltip.setTextAlignment(TextAlignment.LEFT); - cellButton.setTooltip(buttonTooltip); - - cellButton.setToggleGroup(row.toggleGroup); - if (row.toggleGroup.getSelectedToggle() == null) { - cellButton.setSelected(true); - } - - if (field.equals(StandardField.DOI)) { - Button doiButton = IconTheme.JabRefIcons.LOOKUP_IDENTIFIER.asButton(); - HBox.setHgrow(doiButton, Priority.NEVER); - doiButton.prefHeightProperty().bind(cellButton.heightProperty()); - doiButton.setMinHeight(Control.USE_PREF_SIZE); - doiButton.setMaxHeight(Control.USE_PREF_SIZE); - - getChildren().add(doiButton); - - doiButton.setOnAction(event -> { - DoiFetcher doiFetcher = new DoiFetcher(preferences.getImportFormatPreferences()); - doiButton.setDisable(true); - addSource(Localization.lang("From DOI"), () -> { - try { - return doiFetcher.performSearchById(content).get(); - } catch (FetcherException | NoSuchElementException e) { - LOGGER.warn("Failed to fetch BibEntry for DOI {}", content, e); - return null; - } - }); + UiTaskExecutor.runInJavaFXThread( + () -> { + FieldRow row = fieldRows.get(field); + + prefWidthProperty() + .bind( + ((Region) supplierHeader.getChildren().get(columnIndex)) + .widthProperty()); + setMinWidth(Control.USE_PREF_SIZE); + setMaxWidth(Control.USE_PREF_SIZE); + prefHeightProperty() + .bind( + ((Region) fieldEditor.getChildren().get(row.rowIndex)) + .heightProperty()); + setMinHeight(Control.USE_PREF_SIZE); + setMaxHeight(Control.USE_PREF_SIZE); + + // Button + ToggleButton cellButton = new ToggleButton(); + cellButton.prefHeightProperty().bind(heightProperty()); + cellButton.setMinHeight(Control.USE_PREF_SIZE); + cellButton.setMaxHeight(Control.USE_PREF_SIZE); + cellButton.setGraphicTextGap(0); + getChildren().add(cellButton); + cellButton.maxWidthProperty().bind(widthProperty()); + HBox.setHgrow(cellButton, Priority.ALWAYS); + + // Text + DiffHighlightingEllipsingTextFlow buttonText = + new DiffHighlightingEllipsingTextFlow( + content, + viewModel + .mergedEntryProperty() + .get() + .getFieldBinding(field) + .asOrdinary(), + diffMode.valueProperty()); + + buttonText.maxWidthProperty().bind(widthProperty().add(-10)); + buttonText.maxHeightProperty().bind(heightProperty()); + cellButton.setGraphic(buttonText); + cellButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + cellButton.setContentDisplay(ContentDisplay.CENTER); + + // Tooltip + Tooltip buttonTooltip = new Tooltip(content); + buttonTooltip.setWrapText(true); + buttonTooltip.prefWidthProperty().bind(widthProperty()); + buttonTooltip.setTextAlignment(TextAlignment.LEFT); + cellButton.setTooltip(buttonTooltip); + + cellButton.setToggleGroup(row.toggleGroup); + if (row.toggleGroup.getSelectedToggle() == null) { + cellButton.setSelected(true); + } + + if (field.equals(StandardField.DOI)) { + Button doiButton = IconTheme.JabRefIcons.LOOKUP_IDENTIFIER.asButton(); + HBox.setHgrow(doiButton, Priority.NEVER); + doiButton.prefHeightProperty().bind(cellButton.heightProperty()); + doiButton.setMinHeight(Control.USE_PREF_SIZE); + doiButton.setMaxHeight(Control.USE_PREF_SIZE); + + getChildren().add(doiButton); + + doiButton.setOnAction( + event -> { + DoiFetcher doiFetcher = + new DoiFetcher( + preferences.getImportFormatPreferences()); + doiButton.setDisable(true); + addSource( + Localization.lang("From DOI"), + () -> { + try { + return doiFetcher + .performSearchById(content) + .get(); + } catch (FetcherException + | NoSuchElementException e) { + LOGGER.warn( + "Failed to fetch BibEntry for DOI {}", + content, + e); + return null; + } + }); + }); + } }); - } - }); } public String getContent() { @@ -343,7 +414,8 @@ public void addSource(String title, BibEntry entry) { } public void addSource(String title, Supplier supplier) { - viewModel.addSource(new MultiMergeEntriesViewModel.EntrySource(title, supplier, taskExecutor)); + viewModel.addSource( + new MultiMergeEntriesViewModel.EntrySource(title, supplier, taskExecutor)); } private class FieldRow { @@ -353,8 +425,10 @@ private class FieldRow { 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,16 +444,28 @@ public FieldRow(Field field, int rowIndex) { addRow(field); - fieldEditorCell.addEventFilter(KeyEvent.KEY_PRESSED, event -> toggleGroup.selectToggle(null)); - - toggleGroup.selectedToggleProperty().addListener((obs, oldValue, newValue) -> { - if (newValue == null) { - viewModel.mergedEntryProperty().get().setField(field, ""); - } else { - viewModel.mergedEntryProperty().get().setField(field, ((DiffHighlightingEllipsingTextFlow) ((ToggleButton) newValue).getGraphic()).getFullText()); - headerToggleGroup.selectToggle(null); - } - }); + fieldEditorCell.addEventFilter( + KeyEvent.KEY_PRESSED, event -> toggleGroup.selectToggle(null)); + + toggleGroup + .selectedToggleProperty() + .addListener( + (obs, oldValue, newValue) -> { + if (newValue == null) { + viewModel.mergedEntryProperty().get().setField(field, ""); + } else { + viewModel + .mergedEntryProperty() + .get() + .setField( + field, + ((DiffHighlightingEllipsingTextFlow) + ((ToggleButton) newValue) + .getGraphic()) + .getFullText()); + headerToggleGroup.selectToggle(null); + } + }); } /** @@ -390,7 +476,8 @@ public FieldRow(Field field, int rowIndex) { private void addRow(Field field) { VBox.setVgrow(fieldEditorCell, Priority.ALWAYS); - fieldBinding = viewModel.mergedEntryProperty().get().getFieldBinding(field).asOrdinary(); + fieldBinding = + viewModel.mergedEntryProperty().get().getFieldBinding(field).asOrdinary(); BindingsHelper.bindBidirectional( fieldEditorCell.textProperty(), fieldBinding, diff --git a/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesViewModel.java b/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesViewModel.java index e75bc054bef1..899e4699c628 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesViewModel.java +++ b/src/main/java/org/jabref/gui/mergeentries/MultiMergeEntriesViewModel.java @@ -1,8 +1,5 @@ package org.jabref.gui.mergeentries; -import java.util.Map; -import java.util.function.Supplier; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -20,26 +17,32 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; +import java.util.Map; +import java.util.function.Supplier; + public class MultiMergeEntriesViewModel extends AbstractViewModel { - private final ListProperty entries = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty entries = + new SimpleListProperty<>(FXCollections.observableArrayList()); private final ObjectProperty mergedEntry = new SimpleObjectProperty<>(new BibEntry()); - private final ListProperty failedSuppliers = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty failedSuppliers = + new SimpleListProperty<>(FXCollections.observableArrayList()); public void addSource(EntrySource entrySource) { if (!entrySource.isLoading.getValue()) { updateFields(entrySource.entry.get()); } else { - entrySource.isLoading.addListener((observable, oldValue, newValue) -> { - if (!newValue) { - updateFields(entrySource.entry.get()); - if (entrySource.entryProperty().get() == null) { - failedSuppliers.add(entrySource.titleProperty().get()); - } - } - }); + entrySource.isLoading.addListener( + (observable, oldValue, newValue) -> { + if (!newValue) { + updateFields(entrySource.entry.get()); + if (entrySource.entryProperty().get() == null) { + failedSuppliers.add(entrySource.titleProperty().get()); + } + } + }); } entries.add(entrySource); } @@ -80,16 +83,18 @@ public static class EntrySource { private final ObjectProperty entry = new SimpleObjectProperty<>(); private final BooleanProperty isLoading = new SimpleBooleanProperty(false); - public EntrySource(String title, Supplier entrySupplier, TaskExecutor taskExecutor) { + 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); + .onSuccess( + value -> { + entry.set(value); + isLoading.set(false); + }) + .executeWith(taskExecutor); } public EntrySource(String title, BibEntry entry) { diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java index e74420f9fa4e..1bbe140e502e 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowView.java @@ -1,11 +1,14 @@ package org.jabref.gui.mergeentries.newmergedialog; +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; +import org.fxmisc.richtext.StyleClassedTextArea; import org.jabref.gui.mergeentries.newmergedialog.FieldRowViewModel.Selection; import org.jabref.gui.mergeentries.newmergedialog.cell.FieldNameCell; import org.jabref.gui.mergeentries.newmergedialog.cell.FieldValueCell; @@ -19,9 +22,6 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; - -import com.tobiasdiez.easybind.EasyBind; -import org.fxmisc.richtext.StyleClassedTextArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,35 +43,50 @@ public class FieldRowView { private GridPane parent; - public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEntry mergedEntry, FieldMergerFactory fieldMergerFactory, GuiPreferences preferences, int rowIndex) { - viewModel = new FieldRowViewModel(field, leftEntry, rightEntry, mergedEntry, fieldMergerFactory); + 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); leftValueCell = new FieldValueCell(viewModel.getLeftFieldValue(), rowIndex, preferences); 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. + // 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; - } - }); + leftValueCell + .parentProperty() + .addListener( + e -> { + if (leftValueCell.getParent() instanceof GridPane grid) { + parent = grid; + } + }); if (FieldMergerFactory.canMerge(field)) { ToggleMergeUnmergeButton toggleMergeUnmergeButton = new ToggleMergeUnmergeButton(field); toggleMergeUnmergeButton.setCanMerge(!viewModel.hasEqualLeftAndRightValues()); fieldNameCell.addSideButton(toggleMergeUnmergeButton); - EasyBind.listen(toggleMergeUnmergeButton.fieldStateProperty(), ((observableValue, old, fieldState) -> { - LOGGER.debug("Field merge state is {} for field {}", fieldState, field); - if (fieldState == ToggleMergeUnmergeButton.FieldState.MERGED) { - viewModel.mergeFields(); - } else { - viewModel.unmergeFields(); - } - })); + EasyBind.listen( + toggleMergeUnmergeButton.fieldStateProperty(), + ((observableValue, old, fieldState) -> { + LOGGER.debug("Field merge state is {} for field {}", fieldState, field); + if (fieldState == ToggleMergeUnmergeButton.FieldState.MERGED) { + viewModel.mergeFields(); + } else { + viewModel.unmergeFields(); + } + })); } toggleGroup.getToggles().addAll(leftValueCell, rightValueCell); @@ -80,43 +95,53 @@ public FieldRowView(Field field, BibEntry leftEntry, BibEntry rightEntry, BibEnt leftValueCell.textProperty().bindBidirectional(viewModel.leftFieldValueProperty()); rightValueCell.textProperty().bindBidirectional(viewModel.rightFieldValueProperty()); - EasyBind.subscribe(viewModel.selectionProperty(), selection -> { - if (selection == Selection.LEFT) { - toggleGroup.selectToggle(leftValueCell); - } else if (selection == Selection.RIGHT) { - toggleGroup.selectToggle(rightValueCell); - } else if (selection == Selection.NONE) { - toggleGroup.selectToggle(null); - } - }); - - EasyBind.subscribe(toggleGroup.selectedToggleProperty(), selectedToggle -> { - if (selectedToggle == leftValueCell) { - selectLeftValue(); - } else if (selectedToggle == rightValueCell) { - selectRightValue(); - } else { - selectNone(); - } - }); + EasyBind.subscribe( + viewModel.selectionProperty(), + selection -> { + if (selection == Selection.LEFT) { + toggleGroup.selectToggle(leftValueCell); + } else if (selection == Selection.RIGHT) { + toggleGroup.selectToggle(rightValueCell); + } else if (selection == Selection.NONE) { + toggleGroup.selectToggle(null); + } + }); + + EasyBind.subscribe( + toggleGroup.selectedToggleProperty(), + selectedToggle -> { + if (selectedToggle == leftValueCell) { + selectLeftValue(); + } else if (selectedToggle == rightValueCell) { + selectRightValue(); + } else { + selectNone(); + } + }); // 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 { - rightValueCell.setVisible(true); - GridPane.setColumnSpan(leftValueCell, 1); - } - }); - - EasyBind.listen(viewModel.hasEqualLeftAndRightBinding(), (obs, old, isEqual) -> { - if (isEqual) { - LOGGER.debug("Left and right values are equal, LEFT==RIGHT=={}", viewModel.getLeftFieldValue()); - hideDiff(); - } - }); + EasyBind.subscribe( + viewModel.isFieldsMergedProperty(), + isFieldsMerged -> { + if (isFieldsMerged) { + rightValueCell.setVisible(false); + GridPane.setColumnSpan(leftValueCell, 2); + } else { + rightValueCell.setVisible(true); + GridPane.setColumnSpan(leftValueCell, 1); + } + }); + + EasyBind.listen( + viewModel.hasEqualLeftAndRightBinding(), + (obs, old, isEqual) -> { + if (isEqual) { + LOGGER.debug( + "Left and right values are equal, LEFT==RIGHT=={}", + viewModel.getLeftFieldValue()); + hideDiff(); + } + }); } public void selectLeftValue() { @@ -156,7 +181,9 @@ 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..."); @@ -167,23 +194,28 @@ public void showDiff(ShowDiffConfig diffConfig) { hideDiff(); if (shouldShowDiffs.get()) { if (diffConfig.diffView() == ThreeWayMergeToolbar.DiffView.UNIFIED) { - new UnifiedDiffHighlighter(leftLabel, rightLabel, diffConfig.diffHighlightingMethod()).highlight(); + new UnifiedDiffHighlighter( + leftLabel, rightLabel, diffConfig.diffHighlightingMethod()) + .highlight(); } else { - new SplitDiffHighlighter(leftLabel, rightLabel, diffConfig.diffHighlightingMethod()).highlight(); + new SplitDiffHighlighter(leftLabel, rightLabel, diffConfig.diffHighlightingMethod()) + .highlight(); } } } public void hide() { if (parent != null) { - parent.getChildren().removeAll(leftValueCell, rightValueCell, mergedValueCell, fieldNameCell); + parent.getChildren() + .removeAll(leftValueCell, rightValueCell, mergedValueCell, fieldNameCell); } } public void show() { if (parent != null) { if (!parent.getChildren().contains(leftValueCell)) { - parent.getChildren().addAll(leftValueCell, rightValueCell, mergedValueCell, fieldNameCell); + parent.getChildren() + .addAll(leftValueCell, rightValueCell, mergedValueCell, fieldNameCell); } } } @@ -210,6 +242,16 @@ public boolean hasEqualLeftAndRightValues() { @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/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java index 46886d9bee18..44f3dea25fde 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/FieldRowViewModel.java @@ -1,9 +1,6 @@ package org.jabref.gui.mergeentries.newmergedialog; -import javax.swing.undo.AbstractUndoableEdit; -import javax.swing.undo.CannotRedoException; -import javax.swing.undo.CannotUndoException; -import javax.swing.undo.CompoundEdit; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -21,11 +18,14 @@ import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.types.EntryTypeFactory; import org.jabref.model.strings.StringUtil; - -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.CannotRedoException; +import javax.swing.undo.CannotUndoException; +import javax.swing.undo.CompoundEdit; + public class FieldRowViewModel { public enum Selection { LEFT, @@ -60,7 +60,12 @@ 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; @@ -75,47 +80,69 @@ public FieldRowViewModel(Field field, BibEntry leftEntry, BibEntry rightEntry, B setRightFieldValue(rightEntry.getField(field).orElse("")); } - EasyBind.listen(leftFieldValueProperty(), (obs, old, leftValue) -> leftEntry.setField(field, leftValue)); - EasyBind.listen(rightFieldValueProperty(), (obs, old, rightValue) -> rightEntry.setField(field, rightValue)); - EasyBind.listen(mergedFieldValueProperty(), (obs, old, mergedFieldValue) -> { - if (field.equals(InternalField.TYPE_HEADER)) { - getMergedEntry().setType(EntryTypeFactory.parse(mergedFieldValue)); - } else { - getMergedEntry().setField(field, mergedFieldValue); - } - }); - - hasEqualLeftAndRight = Bindings.createBooleanBinding(this::hasEqualLeftAndRightValues, leftFieldValueProperty(), rightFieldValueProperty()); + EasyBind.listen( + leftFieldValueProperty(), + (obs, old, leftValue) -> leftEntry.setField(field, leftValue)); + EasyBind.listen( + rightFieldValueProperty(), + (obs, old, rightValue) -> rightEntry.setField(field, rightValue)); + EasyBind.listen( + mergedFieldValueProperty(), + (obs, old, mergedFieldValue) -> { + if (field.equals(InternalField.TYPE_HEADER)) { + getMergedEntry().setType(EntryTypeFactory.parse(mergedFieldValue)); + } else { + getMergedEntry().setField(field, mergedFieldValue); + } + }); + + hasEqualLeftAndRight = + Bindings.createBooleanBinding( + this::hasEqualLeftAndRightValues, + leftFieldValueProperty(), + rightFieldValueProperty()); selectNonEmptyValue(); - EasyBind.listen(isFieldsMergedProperty(), (obs, old, areFieldsMerged) -> { - LOGGER.debug("Field are merged: {}", areFieldsMerged); - if (areFieldsMerged) { - selectLeftValue(); - } else { - selectNonEmptyValue(); - } - }); - - EasyBind.subscribe(selectionProperty(), selection -> { - LOGGER.debug("Selecting {}' value for field {}", selection, field.getDisplayName()); - switch (selection) { - case LEFT -> EasyBind.subscribe(leftFieldValueProperty(), this::setMergedFieldValue); - case RIGHT -> EasyBind.subscribe(rightFieldValueProperty(), this::setMergedFieldValue); - } - }); - - EasyBind.subscribe(mergedFieldValueProperty(), mergedValue -> { - LOGGER.debug("Merged value is {} for field {}", mergedValue, field.getDisplayName()); - if (mergedValue.equals(getLeftFieldValue())) { - selectLeftValue(); - } else if (getMergedFieldValue().equals(getRightFieldValue())) { - selectRightValue(); - } else { - selectNone(); - } - }); + EasyBind.listen( + isFieldsMergedProperty(), + (obs, old, areFieldsMerged) -> { + LOGGER.debug("Field are merged: {}", areFieldsMerged); + if (areFieldsMerged) { + selectLeftValue(); + } else { + selectNonEmptyValue(); + } + }); + + EasyBind.subscribe( + selectionProperty(), + selection -> { + LOGGER.debug( + "Selecting {}' value for field {}", selection, field.getDisplayName()); + switch (selection) { + case LEFT -> + EasyBind.subscribe( + leftFieldValueProperty(), this::setMergedFieldValue); + case RIGHT -> + EasyBind.subscribe( + rightFieldValueProperty(), this::setMergedFieldValue); + } + }); + + EasyBind.subscribe( + mergedFieldValueProperty(), + mergedValue -> { + LOGGER.debug( + "Merged value is {} for field {}", mergedValue, field.getDisplayName()); + if (mergedValue.equals(getLeftFieldValue())) { + selectLeftValue(); + } else if (getMergedFieldValue().equals(getRightFieldValue())) { + selectRightValue(); + } else { + selectNone(); + } + }); EasyBind.subscribe(hasEqualLeftAndRightBinding(), this::setIsFieldsMerged); } @@ -178,7 +205,8 @@ public void mergeFields() { if (fieldsMergedEdit.canRedo()) { fieldsMergedEdit.redo(); } else { - fieldsMergedEdit.addEdit(new MergeFieldsUndo(oldLeftFieldValue, oldRightFieldValue, mergedFields)); + fieldsMergedEdit.addEdit( + new MergeFieldsUndo(oldLeftFieldValue, oldRightFieldValue, mergedFields)); fieldsMergedEdit.end(); } } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/GroupDiffMode.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/GroupDiffMode.java index e9e79069e017..e9d28c53a940 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/GroupDiffMode.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/GroupDiffMode.java @@ -5,7 +5,7 @@ public class GroupDiffMode implements DiffMethod { private final String separator; public GroupDiffMode(String separator) { - this.separator = separator; + this.separator = separator; } @Override diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/PersonsNameFieldRowView.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/PersonsNameFieldRowView.java index 500cf583ed2b..be73e7221ac4 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/PersonsNameFieldRowView.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/PersonsNameFieldRowView.java @@ -14,7 +14,14 @@ 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 +36,11 @@ 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/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ShowDiffConfig.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ShowDiffConfig.java index 0f36d9ad5d2d..5f70ac0db73e 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ShowDiffConfig.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ShowDiffConfig.java @@ -2,7 +2,4 @@ import org.jabref.gui.mergeentries.newmergedialog.toolbar.ThreeWayMergeToolbar.DiffView; -public record ShowDiffConfig( - DiffView diffView, - DiffMethod diffHighlightingMethod) { -} +public record ShowDiffConfig(DiffView diffView, DiffMethod diffHighlightingMethod) {} diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeHeaderView.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeHeaderView.java index 9dcf32414eb0..199c6c8631d6 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeHeaderView.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeHeaderView.java @@ -25,19 +25,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)); } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java index b4d36e34e601..2ee5f3001e82 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeView.java @@ -1,8 +1,5 @@ package org.jabref.gui.mergeentries.newmergedialog; -import java.util.ArrayList; -import java.util.List; - import javafx.scene.control.ScrollPane; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; @@ -20,6 +17,9 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldProperty; +import java.util.ArrayList; +import java.util.List; + @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") public class ThreeWayMergeView extends VBox { public static final int GRID_COLUMN_MIN_WIDTH = 250; @@ -28,9 +28,12 @@ public class ThreeWayMergeView extends VBox { 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; @@ -44,13 +47,25 @@ public class ThreeWayMergeView extends VBox { 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; - getStylesheets().add(ThreeWayMergeView.class.getResource("ThreeWayMergeView.css").toExternalForm()); - viewModel = new ThreeWayMergeViewModel((BibEntry) leftEntry.clone(), (BibEntry) rightEntry.clone(), leftHeader, rightHeader); + getStylesheets() + .add(ThreeWayMergeView.class.getResource("ThreeWayMergeView.css").toExternalForm()); + viewModel = + new ThreeWayMergeViewModel( + (BibEntry) leftEntry.clone(), + (BibEntry) rightEntry.clone(), + leftHeader, + rightHeader); this.fieldMergerFactory = new FieldMergerFactory(preferences.getBibEntryPreferences()); - this.keywordSeparator = preferences.getBibEntryPreferences().getKeywordSeparator().toString(); + this.keywordSeparator = + preferences.getBibEntryPreferences().getKeywordSeparator().toString(); mergeGridPane = new GridPane(); scrollPane = new ScrollPane(); @@ -101,10 +116,16 @@ 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))) { - row.showDiff(new ShowDiffConfig(toolbar.getDiffView(), new GroupDiffMode(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 { - row.showDiff(new ShowDiffConfig(toolbar.getDiffView(), toolbar.getDiffHighlightingMethod())); + row.showDiff( + new ShowDiffConfig( + toolbar.getDiffView(), toolbar.getDiffHighlightingMethod())); } } } else { @@ -113,10 +134,13 @@ private void updateDiff() { } private void initializeHeaderView() { - headerView.getColumnConstraints().addAll(fieldNameColumnConstraints, - leftEntryColumnConstraints, - rightEntryColumnConstraints, - mergedEntryColumnConstraints); + headerView + .getColumnConstraints() + .addAll( + fieldNameColumnConstraints, + leftEntryColumnConstraints, + rightEntryColumnConstraints, + mergedEntryColumnConstraints); } private void initializeScrollPane() { @@ -133,7 +157,13 @@ 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); @@ -151,9 +181,25 @@ private void addRow(int fieldIndex) { FieldRowView fieldRow; if (field.getProperties().contains(FieldProperty.PERSON_NAMES)) { - fieldRow = new PersonsNameFieldRowView(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); + fieldRow = + new FieldRowView( + field, + getLeftEntry(), + getRightEntry(), + getMergedEntry(), + fieldMergerFactory, + preferences, + fieldIndex); } fieldRows.add(fieldIndex, fieldRow); diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeViewModel.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeViewModel.java index 9c5b35419058..a30f91287ad3 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeViewModel.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/ThreeWayMergeViewModel.java @@ -1,11 +1,5 @@ package org.jabref.gui.mergeentries.newmergedialog; -import java.util.Comparator; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -19,6 +13,12 @@ import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.InternalField; +import java.util.Comparator; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + public class ThreeWayMergeViewModel extends AbstractViewModel { private final ObjectProperty leftEntry = new SimpleObjectProperty<>(); @@ -29,7 +29,8 @@ public class ThreeWayMergeViewModel extends AbstractViewModel { private final ObservableList visibleFields = FXCollections.observableArrayList(); - public ThreeWayMergeViewModel(BibEntry leftEntry, BibEntry rightEntry, String leftHeader, String rightHeader) { + public ThreeWayMergeViewModel( + BibEntry leftEntry, BibEntry rightEntry, String leftHeader, String rightHeader) { Objects.requireNonNull(leftEntry, "Left entry is required"); Objects.requireNonNull(rightEntry, "Right entry is required"); Objects.requireNonNull(leftHeader, "Left header entry is required"); @@ -42,9 +43,9 @@ 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() { diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/CopyFieldValueCommand.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/CopyFieldValueCommand.java index 5f8293f18a3b..680ba84dd104 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/CopyFieldValueCommand.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/CopyFieldValueCommand.java @@ -1,10 +1,10 @@ package org.jabref.gui.mergeentries.newmergedialog.cell; -import java.util.Objects; - import org.jabref.gui.ClipBoardManager; import org.jabref.gui.actions.SimpleCommand; +import java.util.Objects; + public class CopyFieldValueCommand extends SimpleCommand { private final String fieldValue; private final ClipBoardManager clipBoardManager; diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCell.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCell.java index d93b6d4f45fe..6bef122f5a2c 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCell.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCell.java @@ -1,5 +1,7 @@ package org.jabref.gui.mergeentries.newmergedialog.cell; +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.css.PseudoClass; @@ -19,6 +21,8 @@ import javafx.scene.layout.Priority; import javafx.scene.paint.Color; +import org.fxmisc.flowless.VirtualizedScrollPane; +import org.fxmisc.richtext.StyleClassedTextArea; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.fieldeditors.URLUtil; import org.jabref.gui.icon.IconTheme; @@ -26,10 +30,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.strings.StringUtil; - -import com.tobiasdiez.easybind.EasyBind; -import org.fxmisc.flowless.VirtualizedScrollPane; -import org.fxmisc.richtext.StyleClassedTextArea; import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.materialdesign2.MaterialDesignC; import org.slf4j.Logger; @@ -51,7 +51,8 @@ public class FieldValueCell extends ThreeWayMergeCell implements Toggle { private final ActionFactory factory; private final StyleClassedTextArea label = new StyleClassedTextArea(); - private final VirtualizedScrollPane scrollPane = new VirtualizedScrollPane<>(label); + private final VirtualizedScrollPane scrollPane = + new VirtualizedScrollPane<>(label); HBox labelBox = new HBox(scrollPane); private final HBox selectionBox = new HBox(); @@ -65,10 +66,12 @@ public FieldValueCell(String text, int rowIndex, GuiPreferences preferences) { this.factory = new ActionFactory(); this.viewModel = new FieldValueCellViewModel(text); - EasyBind.listen(viewModel.selectedProperty(), (observable, old, isSelected) -> { - pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, isSelected); - getToggleGroup().selectToggle(FieldValueCell.this); - }); + EasyBind.listen( + viewModel.selectedProperty(), + (observable, old, isSelected) -> { + pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, isSelected); + getToggleGroup().selectToggle(FieldValueCell.this); + }); viewModel.fieldValueProperty().bind(textProperty()); @@ -81,11 +84,12 @@ private void initialize() { initializeLabel(); initializeSelectionBox(); initializeActions(); - setOnMouseClicked(e -> { - if (!isDisabled()) { - setSelected(true); - } - }); + setOnMouseClicked( + e -> { + if (!isDisabled()) { + setSelected(true); + } + }); selectionBox.getChildren().addAll(labelBox, actionsContainer); getChildren().setAll(selectionBox); @@ -105,10 +109,12 @@ private void initializeLabel() { label.prefHeightProperty().bind(label.totalHeightEstimateProperty().orElseConst(-1d)); // Fix text area consuming scroll events before they reach the outer scrollable - label.addEventFilter(ScrollEvent.SCROLL, e -> { - e.consume(); - FieldValueCell.this.fireEvent(e.copyFor(e.getSource(), FieldValueCell.this)); - }); + label.addEventFilter( + ScrollEvent.SCROLL, + e -> { + e.consume(); + FieldValueCell.this.fireEvent(e.copyFor(e.getSource(), FieldValueCell.this)); + }); } private void initializeActions() { @@ -130,7 +136,9 @@ 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 +151,23 @@ 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; } @@ -159,17 +178,19 @@ 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)) { - e.consume(); - } else if (e.getEventType() == MouseEvent.MOUSE_PRESSED) { - if (e.getClickCount() > 1) { - e.consume(); - } - } - }); + label.addEventFilter( + MouseEvent.ANY, + e -> { + 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) { + if (e.getClickCount() > 1) { + e.consume(); + } + } + }); } @Override diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCellViewModel.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCellViewModel.java index 7587d1e261b2..d95536d2a734 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCellViewModel.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/FieldValueCellViewModel.java @@ -10,7 +10,8 @@ public class FieldValueCellViewModel { private final StringProperty fieldValue = new SimpleStringProperty(); - private final BooleanProperty selected = new SimpleBooleanProperty(FieldValueCell.class, "selected"); + private final BooleanProperty selected = + new SimpleBooleanProperty(FieldValueCell.class, "selected"); private final ObjectProperty toggleGroup = new SimpleObjectProperty<>(); public FieldValueCellViewModel(String text) { diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/HeaderCell.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/HeaderCell.java index 70ca433229ad..16568ed77f8a 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/HeaderCell.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/HeaderCell.java @@ -23,6 +23,11 @@ private void initialize() { private void initializeLabel() { label.textProperty().bind(textProperty()); - label.setPadding(new Insets(getPadding().getTop(), getPadding().getRight(), getPadding().getBottom(), 16)); + label.setPadding( + new Insets( + getPadding().getTop(), + getPadding().getRight(), + getPadding().getBottom(), + 16)); } } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/MergedFieldCell.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/MergedFieldCell.java index 89c591ee0acd..12999f9beb99 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/MergedFieldCell.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/MergedFieldCell.java @@ -6,9 +6,8 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; -import org.jabref.gui.util.BindingsHelper; - import org.fxmisc.richtext.StyleClassedTextArea; +import org.jabref.gui.util.BindingsHelper; public class MergedFieldCell extends ThreeWayMergeCell { private static final String DEFAULT_STYLE_CLASS = "merged-field"; @@ -27,10 +26,11 @@ 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); @@ -38,9 +38,11 @@ private void initializeTextArea() { textArea.setPadding(new Insets(8)); HBox.setHgrow(textArea, Priority.ALWAYS); - textArea.addEventFilter(ScrollEvent.SCROLL, e -> { - e.consume(); - MergedFieldCell.this.fireEvent(e.copyFor(e.getSource(), MergedFieldCell.this)); - }); + textArea.addEventFilter( + ScrollEvent.SCROLL, + e -> { + e.consume(); + MergedFieldCell.this.fireEvent(e.copyFor(e.getSource(), MergedFieldCell.this)); + }); } } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/OpenExternalLinkAction.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/OpenExternalLinkAction.java index b751d9c2f09d..8ddb85d86706 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/OpenExternalLinkAction.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/OpenExternalLinkAction.java @@ -1,16 +1,15 @@ package org.jabref.gui.mergeentries.newmergedialog.cell; -import java.io.IOException; -import java.net.URI; - import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.model.entry.identifier.DOI; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.URI; + /** * A command for opening DOIs and URLs. This was created primarily for simplifying {@link FieldValueCell}. */ @@ -21,7 +20,8 @@ public class OpenExternalLinkAction extends SimpleCommand { private final String urlOrDoi; - public OpenExternalLinkAction(String urlOrDoi, ExternalApplicationsPreferences externalApplicationsPreferences) { + public OpenExternalLinkAction( + String urlOrDoi, ExternalApplicationsPreferences externalApplicationsPreferences) { this.externalApplicationPreferences = externalApplicationsPreferences; this.urlOrDoi = urlOrDoi; } @@ -32,15 +32,12 @@ public void execute() { if (DOI.isValid(urlOrDoi)) { NativeDesktop.openBrowser( DOI.parse(urlOrDoi) - .flatMap(DOI::getExternalURI) - .map(URI::toString) - .orElse(""), - externalApplicationPreferences - - ); + .flatMap(DOI::getExternalURI) + .map(URI::toString) + .orElse(""), + externalApplicationPreferences); } else { - NativeDesktop.openBrowser(urlOrDoi, externalApplicationPreferences - ); + NativeDesktop.openBrowser(urlOrDoi, externalApplicationPreferences); } } catch (IOException e) { LOGGER.warn("Cannot open the given external link '{}'", urlOrDoi, e); diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCell.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCell.java index 1c9df85a6bdd..b64e4a19ebdd 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCell.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCell.java @@ -1,11 +1,11 @@ package org.jabref.gui.mergeentries.newmergedialog.cell; +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.property.StringProperty; import javafx.css.PseudoClass; import javafx.scene.layout.HBox; -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"; @@ -18,12 +18,16 @@ 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() { diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCellViewModel.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCellViewModel.java index e5674a602512..f1de30a25203 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCellViewModel.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/ThreeWayMergeCellViewModel.java @@ -1,14 +1,14 @@ package org.jabref.gui.mergeentries.newmergedialog.cell; +import static org.jabref.gui.mergeentries.newmergedialog.cell.ThreeWayMergeCell.HEADER_ROW; + +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import com.tobiasdiez.easybind.EasyBind; - -import static org.jabref.gui.mergeentries.newmergedialog.cell.ThreeWayMergeCell.HEADER_ROW; - public class ThreeWayMergeCellViewModel { private final StringProperty text = new SimpleStringProperty(); private final BooleanProperty odd = new SimpleBooleanProperty(ThreeWayMergeCell.class, "odd"); @@ -24,13 +24,17 @@ public ThreeWayMergeCellViewModel(String text, int rowIndex) { } } - EasyBind.subscribe(odd, isOdd -> { - setEven(!isOdd); - }); - - EasyBind.subscribe(even, isEven -> { - setOdd(!isEven); - }); + EasyBind.subscribe( + odd, + isOdd -> { + setEven(!isOdd); + }); + + EasyBind.subscribe( + even, + isEven -> { + setOdd(!isEven); + }); } public String getText() { diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java index 8994bf000372..1e4622b78c97 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java @@ -1,6 +1,6 @@ package org.jabref.gui.mergeentries.newmergedialog.cell.sidebuttons; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -12,23 +12,24 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; -import com.tobiasdiez.easybind.EasyBind; +import java.util.Optional; public class InfoButton extends Button { private final StringProperty infoMessage = new SimpleStringProperty(); private final ActionFactory actionFactory = new ActionFactory(); - private final Action mergeAction = new Action() { - @Override - public Optional getIcon() { - return Optional.of(IconTheme.JabRefIcons.INTEGRITY_INFO); - } + private final Action mergeAction = + new Action() { + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.INTEGRITY_INFO); + } - @Override - public String getText() { - return infoMessage.get(); - } - }; + @Override + public String getText() { + return infoMessage.get(); + } + }; public InfoButton(String infoMessage) { this.infoMessage.setValue(infoMessage); @@ -40,11 +41,14 @@ private void configureButton() { setMaxHeight(Double.MAX_VALUE); setFocusTraversable(false); - actionFactory.configureIconButton(mergeAction, new SimpleCommand() { - @Override - public void execute() { - // The info button is not meant to be clickable that's why this is empty - } - }, this); + actionFactory.configureIconButton( + mergeAction, + new SimpleCommand() { + @Override + public void execute() { + // The info button is not meant to be clickable that's why this is empty + } + }, + this); } } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java index c3048e0eb174..cf21164a65ec 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java @@ -1,7 +1,5 @@ package org.jabref.gui.mergeentries.newmergedialog.cell.sidebuttons; -import java.util.Optional; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -16,8 +14,11 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; +import java.util.Optional; + public class ToggleMergeUnmergeButton extends Button { - private final ObjectProperty fieldState = new SimpleObjectProperty<>(FieldState.UNMERGED); + private final ObjectProperty fieldState = + new SimpleObjectProperty<>(FieldState.UNMERGED); private final BooleanProperty canMerge = new SimpleBooleanProperty(Boolean.TRUE); private final ActionFactory actionFactory = new ActionFactory(); @@ -71,29 +72,31 @@ public void setCanMerge(boolean value) { } private class ToggleMergeCommand extends SimpleCommand { - private final Action mergeAction = new Action() { - @Override - public Optional getIcon() { - return Optional.of(IconTheme.JabRefIcons.MERGE_GROUPS); - } - - @Override - public String getText() { - return Localization.lang("Merge %0", field.getDisplayName()); - } - }; - - private final Action unmergeAction = new Action() { - @Override - public Optional getIcon() { - return Optional.of(IconTheme.JabRefIcons.UNDO); - } - - @Override - public String getText() { - return Localization.lang("Unmerge %0", field.getDisplayName()); - } - }; + private final Action mergeAction = + new Action() { + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.MERGE_GROUPS); + } + + @Override + public String getText() { + return Localization.lang("Merge %0", field.getDisplayName()); + } + }; + + private final Action unmergeAction = + new Action() { + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.UNDO); + } + + @Override + public String getText() { + return Localization.lang("Unmerge %0", field.getDisplayName()); + } + }; @Override public void execute() { @@ -108,6 +111,7 @@ public void execute() { } public enum FieldState { - MERGED, UNMERGED + MERGED, + UNMERGED } } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/DiffHighlighter.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/DiffHighlighter.java index 0e69aed2c1ca..40f7f87af06f 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/DiffHighlighter.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/DiffHighlighter.java @@ -1,20 +1,22 @@ package org.jabref.gui.mergeentries.newmergedialog.diffhighlighter; +import org.fxmisc.richtext.StyleClassedTextArea; +import org.jabref.gui.mergeentries.newmergedialog.DiffMethod; + import java.util.Arrays; import java.util.List; import java.util.Objects; -import org.jabref.gui.mergeentries.newmergedialog.DiffMethod; - -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,7 +44,9 @@ public String getSeparator() { } public enum BasicDiffMethod implements DiffMethod { - WORDS(" "), CHARS(""), COMMA(","); + WORDS(" "), + CHARS(""), + COMMA(","); private final String separator; @@ -61,12 +65,10 @@ protected String join(List stringList) { } enum ChangeType { - ADDITION, DELETION, CHANGE_DELETION + ADDITION, + DELETION, + CHANGE_DELETION } - record Change( - int position, - int spanSize, - ChangeType type) { - } + record Change(int position, int spanSize, ChangeType type) {} } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/SplitDiffHighlighter.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/SplitDiffHighlighter.java index f5194732eda5..e517ffc1e35f 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/SplitDiffHighlighter.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/SplitDiffHighlighter.java @@ -1,12 +1,12 @@ package org.jabref.gui.mergeentries.newmergedialog.diffhighlighter; -import java.util.List; - -import org.jabref.gui.mergeentries.newmergedialog.DiffMethod; - import com.github.difflib.DiffUtils; import com.github.difflib.patch.AbstractDelta; + import org.fxmisc.richtext.StyleClassedTextArea; +import org.jabref.gui.mergeentries.newmergedialog.DiffMethod; + +import java.util.List; /** * A diff highlighter in which changes are split between source and target text view. @@ -14,7 +14,10 @@ */ 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); } @@ -29,7 +32,8 @@ public void highlight() { List sourceTokens = splitString(sourceContent); List targetTokens = splitString(targetContent); - List> deltaList = DiffUtils.diff(sourceTokens, targetTokens).getDeltas(); + List> deltaList = + DiffUtils.diff(sourceTokens, targetTokens).getDeltas(); for (AbstractDelta delta : deltaList) { int affectedSourceTokensPosition = delta.getSource().getPosition(); @@ -37,28 +41,52 @@ 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); + int joinedSourceTokensLength = + affectedTokensInSource.stream() + .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); - int affectedSourceTokensPositionInText = getPositionInText(affectedSourceTokensPosition, sourceTokens); - int affectedTargetTokensPositionInText = getPositionInText(affectedTargetTokensPosition, targetTokens); + int joinedTargetTokensLength = + affectedTokensInTarget.stream() + .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"); + sourceTextview.setStyleClass( + affectedSourceTokensPositionInText, + affectedSourceTokensPositionInText + joinedSourceTokensLength, + "deletion"); case INSERT -> - targetTextview.setStyleClass(affectedTargetTokensPositionInText, affectedTargetTokensPositionInText + joinedTargetTokensLength, "addition"); + targetTextview.setStyleClass( + affectedTargetTokensPositionInText, + affectedTargetTokensPositionInText + joinedTargetTokensLength, + "addition"); } } } @@ -67,7 +95,9 @@ public int getPositionInText(int positionInTokenList, List tokenList) { if (positionInTokenList == 0) { return 0; } else { - return tokenList.stream().limit(positionInTokenList).map(String::length) + return tokenList.stream() + .limit(positionInTokenList) + .map(String::length) .reduce(Integer::sum) .map(value -> value + (getSeparator().length() * positionInTokenList)) .orElse(0); diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/UnifiedDiffHighlighter.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/UnifiedDiffHighlighter.java index c15e0420f3bb..d9672d06094c 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/UnifiedDiffHighlighter.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/diffhighlighter/UnifiedDiffHighlighter.java @@ -1,15 +1,15 @@ package org.jabref.gui.mergeentries.newmergedialog.diffhighlighter; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.jabref.gui.mergeentries.newmergedialog.DiffMethod; - import com.github.difflib.DiffUtils; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.DeltaType; + import org.fxmisc.richtext.StyleClassedTextArea; +import org.jabref.gui.mergeentries.newmergedialog.DiffMethod; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; /** * A diff highlighter in which differences of type {@link DeltaType#CHANGE} are unified and represented by an insertion @@ -17,7 +17,10 @@ */ 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); } @@ -33,7 +36,8 @@ public void highlight() { List targetWords = splitString(targetContent); List unifiedWords = new ArrayList<>(targetWords); - List> deltaList = DiffUtils.diff(sourceWords, targetWords).getDeltas(); + List> deltaList = + DiffUtils.diff(sourceWords, targetWords).getDeltas(); List changeList = new ArrayList<>(); @@ -50,7 +54,9 @@ public void highlight() { unifiedWords.add(deletionPoint, join(deltaSourceWords)); changeList.add(new Change(deletionPoint, 1, ChangeType.CHANGE_DELETION)); - changeList.add(new Change(insertionPoint, deltaTargetWords.size(), ChangeType.ADDITION)); + changeList.add( + new Change( + insertionPoint, deltaTargetWords.size(), ChangeType.ADDITION)); deletionCount++; } case DELETE -> { @@ -62,7 +68,11 @@ 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)); } } } @@ -76,19 +86,24 @@ public void highlight() { appendToTextArea(targetTextview, getSeparator() + word, "unchanged"); } 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"); + appendToTextArea( + targetTextview, getSeparator() + join(changeWords), "deletion"); } else if (change.type() == ChangeType.ADDITION) { if (changeInProgress) { appendToTextArea(targetTextview, join(changeWords), "addition"); changeInProgress = false; } else { - appendToTextArea(targetTextview, getSeparator() + join(changeWords), "addition"); + appendToTextArea( + targetTextview, getSeparator() + join(changeWords), "addition"); } } else if (change.type() == ChangeType.CHANGE_DELETION) { - appendToTextArea(targetTextview, getSeparator() + join(changeWords), "deletion"); + appendToTextArea( + targetTextview, getSeparator() + join(changeWords), "deletion"); changeInProgress = true; } position = (position + changeWords.size()) - 1; diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FieldMergerFactory.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FieldMergerFactory.java index 221ce61c7f89..ba9981135058 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FieldMergerFactory.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FieldMergerFactory.java @@ -21,11 +21,16 @@ public FieldMerger create(Field field) { } else if (field == StandardField.FILE) { return new FileMerger(); } else { - throw new IllegalArgumentException("No implementation found for merging the given field: " + field.getDisplayName()); + 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/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FileMerger.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FileMerger.java index 91063b931f11..0d710e8f42b9 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FileMerger.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/FileMerger.java @@ -1,13 +1,13 @@ package org.jabref.gui.mergeentries.newmergedialog.fieldsmerger; -import java.util.List; - import org.jabref.logic.bibtex.FileFieldWriter; import org.jabref.logic.importer.util.FileFieldParser; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.StandardField; import org.jabref.model.strings.StringUtil; +import java.util.List; + /** * A merger for the {@link StandardField#FILE} field * */ diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/GroupMerger.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/GroupMerger.java index 2b892fcd31a5..f202bda6a81c 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/GroupMerger.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/GroupMerger.java @@ -1,12 +1,12 @@ package org.jabref.gui.mergeentries.newmergedialog.fieldsmerger; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.strings.StringUtil; + import java.util.Arrays; import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.strings.StringUtil; - /** * A merger for the {@link StandardField#GROUPS} field * */ @@ -24,8 +24,8 @@ public String merge(String groupsA, String groupsB) { return groupsA; } else { return Arrays.stream(GROUPS_SEPARATOR_REGEX.split(groupsA + GROUPS_SEPARATOR + groupsB)) - .distinct() - .collect(Collectors.joining(GROUPS_SEPARATOR)); + .distinct() + .collect(Collectors.joining(GROUPS_SEPARATOR)); } } } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/KeywordMerger.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/KeywordMerger.java index 2a453106a6ff..4c64dcea9574 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/KeywordMerger.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/fieldsmerger/KeywordMerger.java @@ -1,11 +1,11 @@ package org.jabref.gui.mergeentries.newmergedialog.fieldsmerger; -import java.util.Objects; - import org.jabref.model.entry.BibEntryPreferences; import org.jabref.model.entry.KeywordList; import org.jabref.model.entry.field.StandardField; +import java.util.Objects; + /** * A merger for the {@link StandardField#KEYWORDS} field * */ diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/toolbar/ThreeWayMergeToolbar.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/toolbar/ThreeWayMergeToolbar.java index 2afea487bf34..56ab3e47a418 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/toolbar/ThreeWayMergeToolbar.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/toolbar/ThreeWayMergeToolbar.java @@ -1,5 +1,12 @@ package org.jabref.gui.mergeentries.newmergedialog.toolbar; +import com.airhacks.afterburner.views.ViewLoader; +import com.google.common.base.Enums; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; + +import jakarta.inject.Inject; + import javafx.beans.binding.BooleanExpression; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; @@ -20,42 +27,26 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.google.common.base.Enums; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.EasyBinding; -import jakarta.inject.Inject; - public class ThreeWayMergeToolbar extends AnchorPane { - @FXML - private RadioButton highlightCharactersRadioButtons; + @FXML private RadioButton highlightCharactersRadioButtons; - @FXML - private RadioButton highlightWordsRadioButton; + @FXML private RadioButton highlightWordsRadioButton; - @FXML - private ToggleGroup diffHighlightingMethodToggleGroup; + @FXML private ToggleGroup diffHighlightingMethodToggleGroup; - @FXML - private ComboBox diffViewComboBox; + @FXML private ComboBox diffViewComboBox; - @FXML - private ComboBox plainTextOrDiffComboBox; + @FXML private ComboBox plainTextOrDiffComboBox; - @FXML - private Button selectLeftEntryValuesButton; + @FXML private Button selectLeftEntryValuesButton; - @FXML - private Button selectRightEntryValuesButton; + @FXML private Button selectRightEntryValuesButton; - @FXML - private CheckBox onlyShowChangedFieldsCheck; + @FXML private CheckBox onlyShowChangedFieldsCheck; - @FXML - private CheckBox applyToAllEntriesCheck; + @FXML private CheckBox applyToAllEntriesCheck; - @Inject - private GuiPreferences preferences; + @Inject private GuiPreferences preferences; private final ObjectProperty diffHighlightingMethod = new SimpleObjectProperty<>(); private final BooleanProperty onlyShowChangedFields = new SimpleBooleanProperty(); @@ -63,58 +54,74 @@ public class ThreeWayMergeToolbar extends AnchorPane { 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<>() { - @Override - public String toString(PlainTextOrDiff plainTextOrDiff) { - return plainTextOrDiff.getValue(); - } + plainTextOrDiffComboBox.setConverter( + new StringConverter<>() { + @Override + public String toString(PlainTextOrDiff plainTextOrDiff) { + return plainTextOrDiff.getValue(); + } - @Override - public PlainTextOrDiff fromString(String string) { - return PlainTextOrDiff.fromString(string); - } - }); + @Override + public PlainTextOrDiff fromString(String string) { + return PlainTextOrDiff.fromString(string); + } + }); diffViewComboBox.disableProperty().bind(notShowDiffProperty()); diffViewComboBox.getItems().addAll(DiffView.values()); - diffViewComboBox.setConverter(new StringConverter<>() { - @Override - public String toString(DiffView diffView) { - return diffView.getValue(); - } + diffViewComboBox.setConverter( + new StringConverter<>() { + @Override + public String toString(DiffView diffView) { + return diffView.getValue(); + } - @Override - public DiffView fromString(String string) { - return DiffView.fromString(string); - } - }); + @Override + public DiffView fromString(String string) { + return DiffView.fromString(string); + } + }); highlightWordsRadioButton.disableProperty().bind(notShowDiffProperty()); highlightCharactersRadioButtons.disableProperty().bind(notShowDiffProperty()); - diffHighlightingMethodToggleGroup.selectedToggleProperty().addListener((observable -> { - if (diffHighlightingMethodToggleGroup.getSelectedToggle().equals(highlightCharactersRadioButtons)) { - diffHighlightingMethod.set(BasicDiffMethod.CHARS); - } else { - diffHighlightingMethod.set(BasicDiffMethod.WORDS); - } - })); - - onlyShowChangedFieldsCheck.selectedProperty().bindBidirectional(preferences.getMergeDialogPreferences().mergeShowChangedFieldOnlyProperty()); + diffHighlightingMethodToggleGroup + .selectedToggleProperty() + .addListener( + (observable -> { + if (diffHighlightingMethodToggleGroup + .getSelectedToggle() + .equals(highlightCharactersRadioButtons)) { + diffHighlightingMethod.set(BasicDiffMethod.CHARS); + } else { + diffHighlightingMethod.set(BasicDiffMethod.WORDS); + } + })); + + 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,21 +130,39 @@ 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); - - boolean highlightWordsRadioButtonValue = diffHighlightingMethodToggleGroup.getSelectedToggle().equals(highlightWordsRadioButton); - preferences.getMergeDialogPreferences().setMergeHighlightWords(highlightWordsRadioButtonValue); + preferences + .getMergeDialogPreferences() + .setMergeShouldShowDiff(plainTextOrDiffComboBox.getValue() == PlainTextOrDiff.Diff); + preferences + .getMergeDialogPreferences() + .setMergeShouldShowUnifiedDiff(diffViewComboBox.getValue() == DiffView.UNIFIED); + + boolean highlightWordsRadioButtonValue = + diffHighlightingMethodToggleGroup + .getSelectedToggle() + .equals(highlightWordsRadioButton); + preferences + .getMergeDialogPreferences() + .setMergeHighlightWords(highlightWordsRadioButtonValue); } public ObjectProperty diffViewProperty() { @@ -157,7 +182,9 @@ public EasyBinding showDiffProperty() { } public void setShowDiff(boolean showDiff) { - plainTextOrDiffComboBox.valueProperty().set(showDiff ? PlainTextOrDiff.Diff : PlainTextOrDiff.PLAIN_TEXT); + plainTextOrDiffComboBox + .valueProperty() + .set(showDiff ? PlainTextOrDiff.Diff : PlainTextOrDiff.PLAIN_TEXT); } public BooleanProperty hideEqualFieldsProperty() { @@ -205,7 +232,8 @@ public void setOnSelectRightEntryValuesButtonClicked(Runnable onClick) { } public enum PlainTextOrDiff { - PLAIN_TEXT(Localization.lang("Plain Text")), Diff(Localization.lang("Show Diff")); + PLAIN_TEXT(Localization.lang("Plain Text")), + Diff(Localization.lang("Show Diff")); private final String value; diff --git a/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java b/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java index 18029ee5f403..18b69ec0b0ea 100644 --- a/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java +++ b/src/main/java/org/jabref/gui/openoffice/AdvancedCiteDialogView.java @@ -1,5 +1,7 @@ package org.jabref.gui.openoffice; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.ButtonType; import javafx.scene.control.RadioButton; @@ -9,8 +11,6 @@ import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - public class AdvancedCiteDialogView extends BaseDialog { @FXML private TextField pageInfo; @@ -20,16 +20,15 @@ public class AdvancedCiteDialogView extends BaseDialog { - if (btn == ButtonType.OK) { - return viewModel; - } - return null; - }); + ViewLoader.view(this).load().setAsDialogPane(this); + + setResultConverter( + btn -> { + if (btn == ButtonType.OK) { + return viewModel; + } + return null; + }); setTitle(Localization.lang("Cite special")); } diff --git a/src/main/java/org/jabref/gui/openoffice/Bootstrap.java b/src/main/java/org/jabref/gui/openoffice/Bootstrap.java index e3aa27fae0f5..795953c55476 100644 --- a/src/main/java/org/jabref/gui/openoffice/Bootstrap.java +++ b/src/main/java/org/jabref/gui/openoffice/Bootstrap.java @@ -20,19 +20,6 @@ package org.jabref.gui.openoffice; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.Random; - import com.sun.star.bridge.UnoUrlResolver; import com.sun.star.bridge.XUnoUrlResolver; import com.sun.star.comp.helper.BootstrapException; @@ -50,46 +37,65 @@ import com.sun.star.uno.UnoRuntime; import com.sun.star.uno.XComponentContext; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.Random; + /** 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 );
    -  * 
    -*/ + * 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 Exception { + private static void insertBasicFactories(XSet xSet, XImplementationLoader xImpLoader) + throws Exception { // insert the factory of the loader xSet.insert(xImpLoader.activate("com.sun.star.comp.loader.JavaLoader", null, null, null)); // insert the factory of the URLResolver - xSet.insert(xImpLoader.activate("com.sun.star.comp.urlresolver.UrlResolver", null, null, null)); + xSet.insert( + xImpLoader.activate("com.sun.star.comp.urlresolver.UrlResolver", null, null, null)); // insert the bridgefactory - xSet.insert(xImpLoader.activate("com.sun.star.comp.bridgefactory.BridgeFactory", null, null, null)); + xSet.insert( + xImpLoader.activate( + "com.sun.star.comp.bridgefactory.BridgeFactory", null, null, null)); // insert the connector - xSet.insert(xImpLoader.activate("com.sun.star.comp.connections.Connector", null, null, null)); + xSet.insert( + xImpLoader.activate("com.sun.star.comp.connections.Connector", null, null, null)); // insert the acceptor - xSet.insert(xImpLoader.activate("com.sun.star.comp.connections.Acceptor", null, null, null)); + xSet.insert( + xImpLoader.activate("com.sun.star.comp.connections.Acceptor", null, null, null)); } /** @@ -120,7 +126,8 @@ public static String[] getDefaultOptions() { * @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); } @@ -131,10 +138,12 @@ public static XComponentContext createInitialComponentContext(Hashtable 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()); + XImplementationLoader xImpLoader = + UnoRuntime.queryInterface(XImplementationLoader.class, new JavaLoader()); XInitialization xInit = UnoRuntime.queryInterface(XInitialization.class, xImpLoader); Object[] args = new Object[] {xSMgr}; xInit.initialize(args); @@ -144,7 +153,9 @@ public static XComponentContext createInitialComponentContext(Map(1); } // add smgr - context_entries.put("/singletons/com.sun.star.lang.theServiceManager", new ComponentContextEntry(null, xSMgr)); + context_entries.put( + "/singletons/com.sun.star.lang.theServiceManager", + new ComponentContextEntry(null, xSMgr)); // ... xxx todo: add standard entries XComponentContext xContext = new ComponentContext(context_entries, null); @@ -166,7 +177,9 @@ public static XComponentContext createInitialComponentContext(Map) null).getServiceManager()); + return UnoRuntime.queryInterface( + XMultiServiceFactory.class, + createInitialComponentContext((Map) null).getServiceManager()); } /** @@ -190,8 +203,10 @@ public static XComponentContext defaultBootstrap_InitialComponentContext() throw * @return a freshly bootstrapped component context. * @throws Exception if things go awry. */ - public static XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, Hashtable bootstrap_parameters) throws Exception { - return defaultBootstrap_InitialComponentContext(ini_file, (Map) bootstrap_parameters); + public static XComponentContext defaultBootstrap_InitialComponentContext( + String ini_file, Hashtable bootstrap_parameters) throws Exception { + return defaultBootstrap_InitialComponentContext( + ini_file, (Map) bootstrap_parameters); } /** @@ -205,7 +220,8 @@ public static XComponentContext defaultBootstrap_InitialComponentContext(String * @return a freshly bootstrapped component context. * @throws Exception if things go awry. */ - public static XComponentContext defaultBootstrap_InitialComponentContext(String ini_file, Map bootstrap_parameters) throws Exception { + public static XComponentContext defaultBootstrap_InitialComponentContext( + String ini_file, Map bootstrap_parameters) throws Exception { // jni convenience: easier to iterate over array than calling Hashtable String pairs[] = null; if (null != bootstrap_parameters) { @@ -243,10 +259,13 @@ public static XComponentContext defaultBootstrap_InitialComponentContext(String } 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) throws Exception; + private static native Object cppuhelper_bootstrap( + String ini_file, String bootstrap_parameters[], ClassLoader loader) throws Exception; /** * Bootstraps the component context from a UNO installation. @@ -269,19 +288,22 @@ public static XComponentContext bootstrap(Path ooPath) throws BootstrapException * @see #getDefaultOptions() * @since LibreOffice 5.1 */ - public static XComponentContext bootstrap(String[] argArray, Path path) throws BootstrapException { + public static XComponentContext bootstrap(String[] argArray, Path path) + throws BootstrapException { XComponentContext xContext = null; try { // create default local component context - XComponentContext xLocalContext = createInitialComponentContext((Map) null); + XComponentContext xLocalContext = + createInitialComponentContext((Map) null); if (xLocalContext == null) { throw new BootstrapException("no local component context!"); } // 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;"; @@ -303,7 +325,8 @@ public static XComponentContext bootstrap(String[] argArray, Path path) throws B XUnoUrlResolver xUrlResolver = UnoUrlResolver.create(xLocalContext); // connection string - String sConnect = "uno:socket,host=localhost,port=2083" + ";urp;StarOffice.ComponentContext"; + String sConnect = + "uno:socket,host=localhost,port=2083" + ";urp;StarOffice.ComponentContext"; // wait until office is started for (int i = 0; ; ++i) { @@ -340,7 +363,8 @@ private static void pipe(final InputStream in, final PrintStream out, final Stri @Override public void run() { try { - BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + BufferedReader r = + new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); for (; ; ) { String s = r.readLine(); diff --git a/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java b/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java index c29f24c84245..7cb762734860 100644 --- a/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java @@ -18,7 +18,10 @@ public CitationEntryViewModel(String refMarkName, String citation, String extraI } public CitationEntryViewModel(CitationEntry citationEntry) { - this(citationEntry.getRefMarkName(), citationEntry.getContext(), citationEntry.getPageInfo().orElse("")); + this( + citationEntry.getRefMarkName(), + citationEntry.getContext(), + citationEntry.getPageInfo().orElse("")); } public CitationEntry toCitationEntry() { diff --git a/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java b/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java index 65cdd357de94..0e9d38e2261c 100644 --- a/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java +++ b/src/main/java/org/jabref/gui/openoffice/DetectOpenOfficeInstallation.java @@ -1,10 +1,5 @@ package org.jabref.gui.openoffice; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; - import org.jabref.gui.DialogService; import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.util.DirectoryDialogConfiguration; @@ -14,6 +9,11 @@ import org.jabref.logic.util.io.FileUtil; import org.jabref.model.strings.StringUtil; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + /** * Tools for automatically detecting OpenOffice or LibreOffice installations. */ @@ -22,7 +22,8 @@ public class DetectOpenOfficeInstallation { private final OpenOfficePreferences openOfficePreferences; private final DialogService dialogService; - public DetectOpenOfficeInstallation(OpenOfficePreferences openOfficePreferences, DialogService dialogService) { + public DetectOpenOfficeInstallation( + OpenOfficePreferences openOfficePreferences, DialogService dialogService) { this.dialogService = dialogService; this.openOfficePreferences = openOfficePreferences; } @@ -32,11 +33,14 @@ 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.")); - DirectoryDialogConfiguration dirDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(NativeDesktop.get().getApplicationDirectory()) - .build(); + 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(); return dialogService.showDirectorySelectionDialog(dirDialogConfiguration); } @@ -49,7 +53,8 @@ private boolean checkAutoDetectedPaths(OpenOfficePreferences openOfficePreferenc if (OS.LINUX && (System.getenv("FLATPAK_SANDBOX_DIR") != null)) { executablePath = OpenOfficePreferences.DEFAULT_LINUX_FLATPAK_EXEC_PATH; } - return !StringUtil.isNullOrEmpty(executablePath) && Files.isRegularFile(Path.of(executablePath)); + return !StringUtil.isNullOrEmpty(executablePath) + && Files.isRegularFile(Path.of(executablePath)); } public boolean setOpenOfficePreferences(Path installDir) { @@ -82,7 +87,8 @@ public Optional chooseAmongInstallations(List installDirs) { return dialogService.showChoiceDialogAndWait( Localization.lang("Choose OpenOffice/LibreOffice executable"), - Localization.lang("Found more than one OpenOffice/LibreOffice executable.") + "\n" + 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); diff --git a/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java b/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java index 986c971c9d0d..3597e484ffb5 100644 --- a/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java +++ b/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogView.java @@ -1,5 +1,9 @@ package org.jabref.gui.openoffice; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.ButtonType; @@ -16,9 +20,6 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.strings.StringUtil; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - public class ManageCitationsDialogView extends BaseDialog { private static final String HTML_BOLD_END_TAG = "
    "; @@ -37,16 +38,15 @@ public class ManageCitationsDialogView extends BaseDialog { 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) { - viewModel.storeSettings(); - } - return null; - }); + setResultConverter( + btn -> { + if (btn == ButtonType.OK) { + viewModel.storeSettings(); + } + return null; + }); setTitle(Localization.lang("Manage citations")); } @@ -56,7 +56,9 @@ private void initialize() { viewModel = new ManageCitationsDialogViewModel(ooBase, dialogService); citation.setCellValueFactory(cellData -> cellData.getValue().citationProperty()); - new ValueTableCellFactory().withGraphic(this::getText).install(citation); + new ValueTableCellFactory() + .withGraphic(this::getText) + .install(citation); extraInfo.setCellValueFactory(cellData -> cellData.getValue().extraInformationProperty()); extraInfo.setEditable(true); @@ -65,15 +67,21 @@ 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 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); diff --git a/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java b/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java index 697212fde54b..d4301a516510 100644 --- a/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.openoffice; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; @@ -11,10 +7,15 @@ import org.jabref.gui.DialogService; import org.jabref.model.openoffice.CitationEntry; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + 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; @@ -35,7 +36,10 @@ 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); } @@ -43,4 +47,3 @@ public ListProperty citationsProperty() { return citations; } } - diff --git a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 637c729a720a..6552b4493f71 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -1,14 +1,16 @@ package org.jabref.gui.openoffice; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.Collectors; +import com.airhacks.afterburner.injection.Injector; +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.comp.helper.BootstrapException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.DisposedException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.uno.Exception; import org.jabref.gui.DialogService; import org.jabref.logic.JabRefException; @@ -45,21 +47,19 @@ import org.jabref.model.openoffice.uno.UnoUndo; import org.jabref.model.openoffice.util.OOResult; import org.jabref.model.openoffice.util.OOVoidResult; - -import com.airhacks.afterburner.injection.Injector; -import com.sun.star.beans.IllegalTypeException; -import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyVetoException; -import com.sun.star.comp.helper.BootstrapException; -import com.sun.star.container.NoSuchElementException; -import com.sun.star.lang.DisposedException; -import com.sun.star.lang.WrappedTargetException; -import com.sun.star.text.XTextCursor; -import com.sun.star.text.XTextDocument; -import com.sun.star.uno.Exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; + /** * Class for manipulating the Bibliography of the currently started document in OpenOffice. */ @@ -75,10 +75,9 @@ public class OOBibBase { private CSLCitationOOAdapter cslCitationOOAdapter; - public OOBibBase(Path loPath, DialogService dialogService, OpenOfficePreferences openOfficePreferences) - throws - BootstrapException, - CreationException { + public OOBibBase( + Path loPath, DialogService dialogService, OpenOfficePreferences openOfficePreferences) + throws BootstrapException, CreationException { this.dialogService = dialogService; this.connection = new OOBibBaseConnect(loPath, dialogService); @@ -86,12 +85,14 @@ public OOBibBase(Path loPath, DialogService dialogService, OpenOfficePreferences this.alwaysAddCitedOnPages = openOfficePreferences.getAlwaysAddCitedOnPages(); } - private void initializeCitationAdapter(XTextDocument doc) throws WrappedTargetException, NoSuchElementException { + private void initializeCitationAdapter(XTextDocument doc) + throws WrappedTargetException, NoSuchElementException { this.cslCitationOOAdapter = new CSLCitationOOAdapter(doc); this.cslCitationOOAdapter.readAndUpdateExistingMarks(); } - public void guiActionSelectDocument(boolean autoSelectForSingle) throws WrappedTargetException, NoSuchElementException { + public void guiActionSelectDocument(boolean autoSelectForSingle) + throws WrappedTargetException, NoSuchElementException { final String errorTitle = Localization.lang("Problem connecting"); try { @@ -101,17 +102,17 @@ public void guiActionSelectDocument(boolean autoSelectForSingle) throws WrappedT OOError.from(ex).showErrorDialog(dialogService); } 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 (this.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("")); } } @@ -160,10 +161,11 @@ 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")); + String msg = + results.stream() + .filter(OOVoidResult::isError) + .map(e -> e.getError().getLocalizedMessage()) + .collect(Collectors.joining("\n\n")); if (msg.isEmpty()) { return OOVoidResult.ok(); } else { @@ -192,7 +194,8 @@ final boolean testDialog(String errorTitle, OOVoidResult... results) { /** * Get the cursor positioned by the user for inserting text. */ - OOResult getUserCursorForTextInsertion(XTextDocument doc, String errorTitle) { + OOResult getUserCursorForTextInsertion( + XTextDocument doc, String errorTitle) { // Get the cursor positioned by the user. XTextCursor cursor = UnoCursor.getViewCursor(doc).orElse(null); @@ -202,9 +205,12 @@ OOResult getUserCursorForTextInsertion(XTextDocument doc, 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."); + 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); @@ -213,13 +219,15 @@ OOResult getUserCursorForTextInsertion(XTextDocument doc, /** * This may move the view cursor. */ - OOResult getFunctionalTextViewCursor(XTextDocument doc, String errorTitle) { + 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."); + + 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()); @@ -227,16 +235,15 @@ OOResult getFunctionalTextViewCursor(XTextDoc return result.mapError(detail -> new OOError(errorTitle, messageOnFailureToObtain)); } - private static OOVoidResult checkRangeOverlaps(XTextDocument doc, OOFrontend frontend) { + private static OOVoidResult checkRangeOverlaps( + XTextDocument doc, OOFrontend frontend) { final String errorTitle = "Overlapping ranges"; boolean requireSeparation = false; int maxReportedOverlaps = 10; try { - return frontend.checkRangeOverlaps(doc, - new ArrayList<>(), - requireSeparation, - maxReportedOverlaps) - .mapError(OOError::from); + 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) { @@ -244,7 +251,8 @@ private static OOVoidResult checkRangeOverlaps(XTextDocument doc, OOFro } } - private static OOVoidResult checkRangeOverlapsWithCursor(XTextDocument doc, OOFrontend frontend) { + private static OOVoidResult checkRangeOverlapsWithCursor( + XTextDocument doc, OOFrontend frontend) { final String errorTitle = "Ranges overlapping with cursor"; List> userRanges; @@ -253,9 +261,7 @@ private static OOVoidResult checkRangeOverlapsWithCursor(XTextDocument boolean requireSeparation = false; OOVoidResult res; try { - res = frontend.checkRangeOverlapsWithCursor(doc, - userRanges, - requireSeparation); + res = frontend.checkRangeOverlapsWithCursor(doc, userRanges, requireSeparation); } catch (NoDocumentException ex) { return OOVoidResult.error(OOError.from(ex).setTitle(errorTitle)); } catch (WrappedTargetException ex) { @@ -265,7 +271,8 @@ private static OOVoidResult checkRangeOverlapsWithCursor(XTextDocument 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")); + new OOError( + xtitle, xtitle + "\n" + res.getError().getLocalizedMessage() + "\n")); } return res.mapError(OOError::from); } @@ -284,24 +291,32 @@ 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" - + " recorded changes."); + 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."); + 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."); + 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(); @@ -321,14 +336,12 @@ OOResult getFrontend(XTextDocument doc) { return OOResult.ok(new OOFrontend(doc)); } 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 { @@ -336,8 +349,8 @@ OOVoidResult databaseIsRequired(List databases, } } - OOVoidResult selectedBibEntryIsRequired(List entries, - Supplier fun) { + OOVoidResult selectedBibEntryIsRequired( + List entries, Supplier fun) { if (entries == null || entries.isEmpty()) { return OOVoidResult.error(fun.get()); } else { @@ -348,31 +361,38 @@ 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"); - } + 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:") + + Localization.lang( + "Please create it in the document or change in the file:") + "\n" + pathToStyleFile; return OOVoidResult.error(new OOError("StyleIsNotKnown", msg)); @@ -381,21 +401,26 @@ private OOVoidResult checkStyleExistsInTheDocument(String familyName, 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"); - } + 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.") + + Localization.lang( + "Please use the latter in the style file below" + + " to avoid localization problems.") + "\n" + pathToStyleFile; return OOVoidResult.error(new OOError("StyleNameIsNotInternal", msg)); @@ -408,27 +433,36 @@ 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)); + results.add( + checkStyleExistsInTheDocument( + UnoStyle.CHARACTER_STYLES, + jStyle.getCitationCharacterFormat(), + doc, + "CitationCharacterFormat", + pathToStyleFile)); } } catch (WrappedTargetException ex) { - results.add(OOVoidResult.error(new OOError("Other error in checkStyleExistsInTheDocument", - ex.getMessage(), - ex))); + results.add( + OOVoidResult.error( + new OOError( + "Other error in checkStyleExistsInTheDocument", + ex.getMessage(), + ex))); } return collectResults("checkStyleExistsInTheDocument failed", results); @@ -506,9 +540,9 @@ public void guiActionApplyCitationEntries(List citationEntries) { } catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (PropertyVetoException - | IllegalTypeException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + | IllegalTypeException + | WrappedTargetException + | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn(errorTitle, ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } @@ -530,18 +564,20 @@ public void guiActionApplyCitationEntries(List citationEntries) { * @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, + if (testDialog( + errorTitle, odoc.asVoidResult(), styleIsRequired(style), selectedBibEntryIsRequired(entries, OOError::noEntriesSelectedForCitation))) { @@ -564,7 +600,8 @@ public void guiActionInsertEntry(List entries, } if (style instanceof JStyle jStyle) { - if (testDialog(errorTitle, + if (testDialog( + errorTitle, checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { return; @@ -577,8 +614,11 @@ 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,21 +635,33 @@ public void guiActionInsertEntry(List entries, if (citationType == CitationType.AUTHORYEAR_PAR) { // "Cite" button - this.cslCitationOOAdapter.insertCitation(cursor.get(), citationStyle, entries, bibDatabaseContext, bibEntryTypesManager); + this.cslCitationOOAdapter.insertCitation( + cursor.get(), + citationStyle, + entries, + bibDatabaseContext, + bibEntryTypesManager); } else if (citationType == CitationType.AUTHORYEAR_INTEXT) { // "Cite in-text" button - this.cslCitationOOAdapter.insertInTextCitation(cursor.get(), citationStyle, entries, bibDatabaseContext, bibEntryTypesManager); + this.cslCitationOOAdapter.insertInTextCitation( + cursor.get(), + citationStyle, + entries, + bibDatabaseContext, + bibEntryTypesManager); } else if (citationType == CitationType.INVISIBLE_CIT) { // "Insert empty citation" this.cslCitationOOAdapter.insertEmpty(cursor.get(), citationStyle, entries); } // If "Automatically sync bibliography when inserting citations" is enabled - syncOptions.ifPresent(options -> guiActionUpdateDocument(options.databases, citationStyle)); + syncOptions.ifPresent( + options -> guiActionUpdateDocument(options.databases, citationStyle)); } else if (style instanceof JStyle jStyle) { // Handle insertion of JStyle citations - EditInsert.insertCitationGroup(doc, + EditInsert.insertCitationGroup( + doc, frontend.get(), cursor.get(), entries, @@ -627,11 +679,11 @@ public void guiActionInsertEntry(List entries, } catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (CreationException - | WrappedTargetException - | IOException - | PropertyVetoException - | IllegalTypeException - | NotRemoveableException ex) { + | WrappedTargetException + | IOException + | PropertyVetoException + | IllegalTypeException + | NotRemoveableException ex) { LOGGER.warn("Could not insert entry", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (Exception e) { @@ -649,7 +701,8 @@ public void guiActionMergeCitationGroups(List databases, OOStyle st if (style instanceof JStyle jStyle) { OOResult odoc = getXTextDocument(); - if (testDialog(errorTitle, + if (testDialog( + errorTitle, odoc.asVoidResult(), styleIsRequired(jStyle), databaseIsRequired(databases, OOError::noDataBaseIsOpen))) { @@ -657,9 +710,11 @@ public void guiActionMergeCitationGroups(List databases, OOStyle st } XTextDocument doc = odoc.get(); - OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); + OOResult fcursor = + getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, + if (testDialog( + errorTitle, fcursor.asVoidResult(), checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { @@ -681,11 +736,11 @@ public void guiActionMergeCitationGroups(List databases, OOStyle st } catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (CreationException - | IllegalTypeException - | NotRemoveableException - | PropertyVetoException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + | 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 { @@ -705,7 +760,8 @@ public void guiActionSeparateCitations(List databases, OOStyle styl if (style instanceof JStyle jStyle) { OOResult odoc = getXTextDocument(); - if (testDialog(errorTitle, + if (testDialog( + errorTitle, odoc.asVoidResult(), styleIsRequired(jStyle), databaseIsRequired(databases, OOError::noDataBaseIsOpen))) { @@ -713,9 +769,11 @@ public void guiActionSeparateCitations(List databases, OOStyle styl } XTextDocument doc = odoc.get(); - OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); + OOResult fcursor = + getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, + if (testDialog( + errorTitle, fcursor.asVoidResult(), checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { @@ -726,7 +784,8 @@ public void guiActionSeparateCitations(List databases, OOStyle styl UnoUndo.enterUndoContext(doc, "Separate citations"); OOFrontend frontend = new OOFrontend(doc); - boolean madeModifications = EditSeparate.separateCitations(doc, frontend, databases, jStyle); + boolean madeModifications = + EditSeparate.separateCitations(doc, frontend, databases, jStyle); if (madeModifications) { UnoCrossRef.refresh(doc); Update.SyncOptions syncOptions = new Update.SyncOptions(databases); @@ -737,11 +796,11 @@ public void guiActionSeparateCitations(List databases, OOStyle styl } catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (CreationException - | IllegalTypeException - | NotRemoveableException - | PropertyVetoException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + | 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 { @@ -758,12 +817,14 @@ public void guiActionSeparateCitations(List databases, OOStyle styl * * @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) { + 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, + if (testDialog( + errorTitle, odoc.asVoidResult(), databaseIsRequired(databases, OOError::noDataBaseIsOpenForExport))) { return FAIL; @@ -785,9 +846,10 @@ public Optional exportCitedHelper(List databases, bool if (!result.newDatabase.hasEntries()) { 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.")); + Localization.lang( + "Your OpenOffice/LibreOffice document references" + + " no citation keys" + + " which could also be found in your current library.")); return FAIL; } @@ -795,7 +857,8 @@ public Optional exportCitedHelper(List databases, bool if (!unresolvedKeys.isEmpty()) { dialogService.showErrorDialogAndWait( Localization.lang("Unable to generate new library"), - Localization.lang("Your OpenOffice/LibreOffice document references" + 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.", @@ -812,8 +875,7 @@ public Optional exportCitedHelper(List databases, bool OOError.from(ex).showErrorDialog(dialogService); } 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); } @@ -832,17 +894,17 @@ 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; } XTextDocument doc = odoc.get(); - OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); + OOResult fcursor = + getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, + if (testDialog( + errorTitle, fcursor.asVoidResult(), checkStylesExistInTheDocument(jStyle, doc), checkIfOpenOfficeIsRecordingChanges(doc))) { @@ -861,19 +923,25 @@ public void guiActionUpdateDocument(List databases, OOStyle style) Update.SyncOptions syncOptions = new Update.SyncOptions(databases); syncOptions .setUpdateBibliography(true) - .setAlwaysAddCitedOnPages(this.alwaysAddCitedOnPages); // TODO: Provide option to user: this is always false - - unresolvedKeys = Update.synchronizeDocument(doc, frontend, jStyle, fcursor.get(), syncOptions); + .setAlwaysAddCitedOnPages( + this + .alwaysAddCitedOnPages); // TODO: Provide option to + // user: this is always false + + unresolvedKeys = + Update.synchronizeDocument( + doc, frontend, jStyle, fcursor.get(), syncOptions); } 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) { @@ -881,8 +949,8 @@ public void guiActionUpdateDocument(List databases, OOStyle style) } catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (CreationException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + | WrappedTargetException + | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Could not update JStyle bibliography", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } @@ -898,9 +966,11 @@ public void guiActionUpdateDocument(List databases, OOStyle style) XTextDocument doc = odoc.get(); - OOResult fcursor = getFunctionalTextViewCursor(doc, errorTitle); + OOResult fcursor = + getFunctionalTextViewCursor(doc, errorTitle); - if (testDialog(errorTitle, + if (testDialog( + errorTitle, fcursor.asVoidResult(), checkIfOpenOfficeIsRecordingChanges(doc))) { return; @@ -925,8 +995,7 @@ public void guiActionUpdateDocument(List databases, OOStyle style) if (citedEntries.isEmpty()) { dialogService.showInformationDialogAndWait( Localization.lang("Bibliography"), - Localization.lang("No cited entries found in the document.") - ); + Localization.lang("No cited entries found in the document.")); return; } @@ -934,17 +1003,22 @@ public void guiActionUpdateDocument(List databases, OOStyle style) BibDatabase bibDatabase = new BibDatabase(citedEntries); BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(bibDatabase); - cslUpdateBibliography.rebuildCSLBibliography(doc, cslCitationOOAdapter, citedEntries, citationStyle, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class)); - } catch (NoDocumentException - | NoSuchElementException e) { + cslUpdateBibliography.rebuildCSLBibliography( + doc, + cslCitationOOAdapter, + citedEntries, + citationStyle, + bibDatabaseContext, + Injector.instantiateModelOrService(BibEntryTypesManager.class)); + } catch (NoDocumentException | NoSuchElementException e) { throw new RuntimeException(e); } finally { UnoUndo.leaveUndoContext(doc); fcursor.get().restore(doc); } } catch (CreationException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + | 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/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java b/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java index 38dc36ad40e5..59544972c344 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBaseConnect.java @@ -1,19 +1,6 @@ package org.jabref.gui.openoffice; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.jabref.gui.DialogService; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.openoffice.NoDocumentFoundException; -import org.jabref.model.openoffice.uno.CreationException; -import org.jabref.model.openoffice.uno.NoDocumentException; -import org.jabref.model.openoffice.uno.UnoCast; -import org.jabref.model.openoffice.uno.UnoTextDocument; -import org.jabref.model.openoffice.util.OOResult; +import static com.sun.star.uno.UnoRuntime.queryInterface; import com.sun.star.bridge.XBridge; import com.sun.star.bridge.XBridgeFactory; @@ -27,10 +14,23 @@ import com.sun.star.lang.XMultiComponentFactory; import com.sun.star.text.XTextDocument; import com.sun.star.uno.XComponentContext; + +import org.jabref.gui.DialogService; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.openoffice.NoDocumentFoundException; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoCast; +import org.jabref.model.openoffice.uno.UnoTextDocument; +import org.jabref.model.openoffice.util.OOResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.sun.star.uno.UnoRuntime.queryInterface; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; /** * Establish connection to a document opened in OpenOffice or LibreOffice. @@ -50,18 +50,13 @@ public class OOBibBaseConnect { private XTextDocument xTextDocument; public OOBibBaseConnect(Path loPath, DialogService dialogService) - throws - BootstrapException, - CreationException { + throws BootstrapException, CreationException { this.dialogService = dialogService; this.xDesktop = simpleBootstrap(loPath); } - private XDesktop simpleBootstrap(Path loPath) - throws - CreationException, - BootstrapException { + private XDesktop simpleBootstrap(Path loPath) throws CreationException, BootstrapException { // Get the office component context: XComponentContext context = Bootstrap.bootstrap(loPath); @@ -84,9 +79,11 @@ private XDesktop simpleBootstrap(Path loPath) 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")); + XBridgeFactory bridgeFactory = + queryInterface( + XBridgeFactory.class, + Bootstrap.createSimpleServiceManager() + .createInstance("com.sun.star.bridge.BridgeFactory")); if (bridgeFactory != null) { for (XBridge bridge : bridgeFactory.getExistingBridges()) { @@ -100,9 +97,7 @@ public static void closeOfficeConnection() { } private static List getTextDocuments(XDesktop desktop) - throws - NoSuchElementException, - WrappedTargetException { + throws NoSuchElementException, WrappedTargetException { List result = new ArrayList<>(); @@ -123,8 +118,8 @@ private static List getTextDocuments(XDesktop desktop) * * @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 { @@ -146,23 +141,20 @@ public String toString() { } } - List viewModel = list.stream() - .map(DocumentTitleViewModel::new) - .collect(Collectors.toList()); + List viewModel = + list.stream().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); + dialogService.showChoiceDialogAndWait( + Localization.lang("Select document"), + Localization.lang("Found documents:"), + Localization.lang("Use selected document"), + viewModel); + + return selectedDocument.map(DocumentTitleViewModel::getXtextDocument).orElse(null); } /** @@ -177,10 +169,7 @@ public String toString() { * 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); @@ -189,8 +178,7 @@ public void selectDocument(boolean 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); + selected = OOBibBaseConnect.selectDocumentDialog(textDocumentList, this.dialogService); } if (selected == null) { @@ -236,9 +224,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"); } diff --git a/src/main/java/org/jabref/gui/openoffice/OOError.java b/src/main/java/org/jabref/gui/openoffice/OOError.java index c8ad7ef53aaf..be0e8a62ec50 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOError.java +++ b/src/main/java/org/jabref/gui/openoffice/OOError.java @@ -1,13 +1,13 @@ package org.jabref.gui.openoffice; +import com.sun.star.lang.DisposedException; + import org.jabref.gui.DialogService; import org.jabref.logic.JabRefException; import org.jabref.logic.l10n.Localization; import org.jabref.logic.openoffice.NoDocumentFoundException; import org.jabref.model.openoffice.uno.NoDocumentException; -import com.sun.star.lang.DisposedException; - class OOError extends JabRefException { private String localizedTitle; @@ -40,19 +40,17 @@ 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"), - Localization.lang("Connection to OpenOffice/LibreOffice has been lost." - + " Please make sure OpenOffice/LibreOffice is running," - + " and try to reconnect."), + Localization.lang( + "Connection to OpenOffice/LibreOffice has been lost." + + " Please make sure OpenOffice/LibreOffice is running," + + " and try to reconnect."), err); } @@ -60,10 +58,11 @@ public static OOError from(DisposedException err) { 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."), + 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); } @@ -71,18 +70,16 @@ public static OOError from(NoDocumentException err) { 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."), + 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); } /* @@ -122,18 +119,21 @@ public static OOError noDataBaseIsOpen() { // noValidStyleSelected public static OOError noValidStyleSelected() { - return new OOError(Localization.lang("No valid style file defined"), + 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( + "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"), + return new OOError( + Localization.lang("No entries selected for citation"), Localization.lang("No bibliography entries are selected for citation.") + "\n" + Localization.lang("Select some before citing.")); diff --git a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java index 267526d0ad76..37d4c363df5f 100644 --- a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java +++ b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java @@ -1,12 +1,9 @@ package org.jabref.gui.openoffice; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import javax.swing.undo.UndoManager; +import com.sun.star.comp.helper.BootstrapException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.property.SimpleObjectProperty; import javafx.concurrent.Task; @@ -63,14 +60,17 @@ import org.jabref.model.openoffice.style.CitationType; import org.jabref.model.openoffice.uno.CreationException; import org.jabref.model.util.FileUpdateMonitor; - -import com.sun.star.comp.helper.BootstrapException; -import com.sun.star.container.NoSuchElementException; -import com.sun.star.lang.WrappedTargetException; -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + /** * Pane to manage the interaction between JabRef and OpenOffice. */ @@ -112,20 +112,21 @@ public class OpenOfficePanel { private final SimpleObjectProperty currentStyleProperty; - public OpenOfficePanel(LibraryTabContainer tabContainer, - GuiPreferences preferences, - OpenOfficePreferences openOfficePreferences, - ExternalApplicationsPreferences externalApplicationsPreferences, - LayoutFormatterPreferences layoutFormatterPreferences, - CitationKeyPatternPreferences citationKeyPatternPreferences, - JournalAbbreviationRepository abbreviationRepository, - UiTaskExecutor taskExecutor, - DialogService dialogService, - 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, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + ClipBoardManager clipBoardManager, + UndoManager undoManager) { this.tabContainer = tabContainer; this.fileUpdateMonitor = fileUpdateMonitor; this.entryTypesManager = entryTypesManager; @@ -151,7 +152,13 @@ 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(); @@ -161,13 +168,13 @@ public OpenOfficePanel(LibraryTabContainer tabContainer, update = new Button(); update.setGraphic(IconTheme.JabRefIcons.ADD_OR_MAKE_BIBLIOGRAPHY.getGraphicNode()); - update.setTooltip(new Tooltip(Localization.lang("Sync OpenOffice/LibreOffice bibliography"))); + update.setTooltip( + new Tooltip(Localization.lang("Sync OpenOffice/LibreOffice bibliography"))); update.setMaxWidth(Double.MAX_VALUE); - loader = new StyleLoader( - openOfficePreferences, - layoutFormatterPreferences, - abbreviationRepository); + loader = + new StyleLoader( + openOfficePreferences, layoutFormatterPreferences, abbreviationRepository); currentStyleProperty = new SimpleObjectProperty<>(currentStyle); @@ -196,9 +203,13 @@ private boolean getOrUpdateTheStyle(String title) { jStyle.ensureUpToDate(); } 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; } @@ -214,64 +225,87 @@ private void initPanel() { connect.setOnAction(e -> connectAutomatically()); manualConnect.setOnAction(e -> connectManually()); - selectDocument.setTooltip(new Tooltip(Localization.lang("Select which open Writer document to work on"))); - selectDocument.setOnAction(e -> { - try { - ooBase.guiActionSelectDocument(false); - } catch (WrappedTargetException - | NoSuchElementException ex) { - throw new RuntimeException(ex); - } - }); + selectDocument.setTooltip( + new Tooltip(Localization.lang("Select which open Writer document to work on"))); + selectDocument.setOnAction( + e -> { + try { + ooBase.guiActionSelectDocument(false); + } catch (WrappedTargetException | NoSuchElementException ex) { + throw new RuntimeException(ex); + } + }); setStyleFile.setMaxWidth(Double.MAX_VALUE); - setStyleFile.setOnAction(event -> { - StyleSelectDialogView styleDialog = new StyleSelectDialogView(loader); - 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"))); + setStyleFile.setOnAction( + event -> { + StyleSelectDialogView styleDialog = new StyleSelectDialogView(loader); + 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"))); pushEntries.setOnAction(e -> pushEntries(CitationType.AUTHORYEAR_PAR, false)); pushEntries.setMaxWidth(Double.MAX_VALUE); - pushEntriesInt.setTooltip(new Tooltip(Localization.lang("Cite selected entries with in-text citation"))); + pushEntriesInt.setTooltip( + new Tooltip(Localization.lang("Cite selected entries with in-text citation"))); pushEntriesInt.setOnAction(e -> 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(e -> pushEntries(CitationType.INVISIBLE_CIT, false)); pushEntriesEmpty.setMaxWidth(Double.MAX_VALUE); - pushEntriesAdvanced.setTooltip(new Tooltip(Localization.lang("Cite selected entries with extra information"))); + pushEntriesAdvanced.setTooltip( + new Tooltip(Localization.lang("Cite selected entries with extra information"))); pushEntriesAdvanced.setOnAction(e -> pushEntries(CitationType.AUTHORYEAR_INTEXT, true)); pushEntriesAdvanced.setMaxWidth(Double.MAX_VALUE); update.setTooltip(new Tooltip(Localization.lang("Make/Sync bibliography"))); - update.setOnAction(event -> { - String title = Localization.lang("Could not update bibliography"); - if (getOrUpdateTheStyle(title)) { - return; - } - List databases = getBaseList(); - ooBase.guiActionUpdateDocument(databases, currentStyle); - }); + update.setOnAction( + event -> { + String title = Localization.lang("Could not update bibliography"); + if (getOrUpdateTheStyle(title)) { + return; + } + List databases = getBaseList(); + ooBase.guiActionUpdateDocument(databases, currentStyle); + }); 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(e -> ooBase.guiActionMergeCitationGroups(getBaseList(), currentStyle)); unmerge.setMaxWidth(Double.MAX_VALUE); @@ -283,12 +317,13 @@ private void initPanel() { settingsB.setContextMenu(settingsMenu); settingsB.setOnAction(e -> settingsMenu.show(settingsB, Side.BOTTOM, 0, 0)); manageCitations.setMaxWidth(Double.MAX_VALUE); - manageCitations.setOnAction(e -> { - ManageCitationsDialogView dialog = new ManageCitationsDialogView(ooBase); - if (dialog.isOkToShowThisDialog()) { - dialogService.showCustomDialogAndWait(dialog); - } - }); + manageCitations.setOnAction( + e -> { + ManageCitationsDialogView dialog = new ManageCitationsDialogView(ooBase); + if (dialog.isOkToShowThisDialog()) { + dialogService.showCustomDialogAndWait(dialog); + } + }); exportCitations.setMaxWidth(Double.MAX_VALUE); exportCitations.setOnAction(event -> exportEntries()); @@ -315,20 +350,22 @@ private void initPanel() { private void exportEntries() { List databases = getBaseList(); boolean returnPartialResult = false; - Optional newDatabase = ooBase.exportCitedHelper(databases, returnPartialResult); + Optional newDatabase = + ooBase.exportCitedHelper(databases, returnPartialResult); if (newDatabase.isPresent()) { BibDatabaseContext databaseContext = new BibDatabaseContext(newDatabase.get()); - LibraryTab libraryTab = LibraryTab.createLibraryTab( - databaseContext, - tabContainer, - dialogService, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, - taskExecutor); + LibraryTab libraryTab = + LibraryTab.createLibraryTab( + databaseContext, + tabContainer, + dialogService, + preferences, + stateManager, + fileUpdateMonitor, + entryTypesManager, + undoManager, + clipBoardManager, + taskExecutor); tabContainer.addTab(libraryTab, true); } } @@ -340,64 +377,98 @@ private List getBaseList() { databases.add(database.getDatabase()); } } else { - databases.add(stateManager.getActiveDatabase() - .map(BibDatabaseContext::getDatabase) - .orElse(new BibDatabase())); + 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 { - Task> taskConnectIfInstalled = new Task<>() { - @Override - protected List call() { - return OpenOfficeFileSearch.detectInstallations(); - } - }; - - taskConnectIfInstalled.setOnSucceeded(evt -> { - List installations = new ArrayList<>(taskConnectIfInstalled.getValue()); - if (installations.isEmpty()) { - officeInstallation.selectInstallationPath().ifPresent(installations::add); - } - Optional chosenInstallationDirectory = officeInstallation.chooseAmongInstallations(installations); - if (chosenInstallationDirectory.isPresent() && officeInstallation.setOpenOfficePreferences(chosenInstallationDirectory.get())) { - connect(); - } - }); - - taskConnectIfInstalled.setOnFailed(value -> dialogService.showErrorDialogAndWait(Localization.lang("Autodetection failed"), Localization.lang("Autodetection failed"), taskConnectIfInstalled.getException())); - - dialogService.showProgressDialog(Localization.lang("Autodetecting paths..."), Localization.lang("Autodetecting paths..."), taskConnectIfInstalled); + Task> taskConnectIfInstalled = + new Task<>() { + @Override + protected List call() { + return OpenOfficeFileSearch.detectInstallations(); + } + }; + + taskConnectIfInstalled.setOnSucceeded( + evt -> { + List installations = + new ArrayList<>(taskConnectIfInstalled.getValue()); + if (installations.isEmpty()) { + officeInstallation + .selectInstallationPath() + .ifPresent(installations::add); + } + Optional chosenInstallationDirectory = + officeInstallation.chooseAmongInstallations(installations); + if (chosenInstallationDirectory.isPresent() + && officeInstallation.setOpenOfficePreferences( + chosenInstallationDirectory.get())) { + connect(); + } + }); + + taskConnectIfInstalled.setOnFailed( + value -> + dialogService.showErrorDialogAndWait( + Localization.lang("Autodetection failed"), + Localization.lang("Autodetection failed"), + taskConnectIfInstalled.getException())); + + dialogService.showProgressDialog( + Localization.lang("Autodetecting paths..."), + Localization.lang("Autodetecting paths..."), + taskConnectIfInstalled); taskExecutor.execute(taskConnectIfInstalled); } } private void connectManually() { - var fileDialogConfiguration = new DirectoryDialogConfiguration.Builder().withInitialDirectory(System.getProperty("user.home")).build(); - Optional selectedPath = dialogService.showDirectorySelectionDialog(fileDialogConfiguration); + var 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); + 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.")); + dialogService.showErrorDialogAndWait( + Localization.lang("Could not connect to running OpenOffice/LibreOffice."), + Localization.lang( + "If connecting manually, please verify program and library paths.")); } } @@ -424,61 +495,87 @@ private void updateButtonAvailability() { } private void connect() { - Task connectTask = new Task<>() { - @Override - protected OOBibBase call() throws BootstrapException, CreationException { - updateProgress(ProgressBar.INDETERMINATE_PROGRESS, ProgressBar.INDETERMINATE_PROGRESS); - - Path path = Path.of(openOfficePreferences.getExecutablePath()); - return createBibBase(path); - } - }; - - connectTask.setOnSucceeded(value -> { - ooBase = connectTask.getValue(); - - try { - ooBase.guiActionSelectDocument(true); - } catch (WrappedTargetException - | NoSuchElementException e) { - throw new RuntimeException(e); - } - - // Enable actions that depend on a connection - updateButtonAvailability(); - }); + Task connectTask = + new Task<>() { + @Override + protected OOBibBase call() throws BootstrapException, CreationException { + updateProgress( + ProgressBar.INDETERMINATE_PROGRESS, + ProgressBar.INDETERMINATE_PROGRESS); + + Path path = Path.of(openOfficePreferences.getExecutablePath()); + return createBibBase(path); + } + }; - connectTask.setOnFailed(value -> { - Throwable ex = connectTask.getException(); - LOGGER.error("autodetect failed", ex); - switch (ex) { - case UnsatisfiedLinkError unsatisfiedLinkError -> { - LOGGER.warn("Could not connect to running OpenOffice/LibreOffice", unsatisfiedLinkError); + connectTask.setOnSucceeded( + value -> { + ooBase = connectTask.getValue(); - 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); + try { + ooBase.guiActionSelectDocument(true); + } catch (WrappedTargetException | NoSuchElementException e) { + throw new RuntimeException(e); + } - 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("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); - } - }); + // Enable actions that depend on a connection + updateButtonAvailability(); + }); + + connectTask.setOnFailed( + value -> { + Throwable ex = connectTask.getException(); + LOGGER.error("autodetect failed", ex); + switch (ex) { + 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.")); + } + 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.") + + "\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:"), + 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); + } + }); - 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); } @@ -496,9 +593,7 @@ private static CitationType citationTypeFromOptions(boolean withText, boolean in 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) { @@ -508,8 +603,8 @@ private void pushEntries(CitationType citationType, boolean addPageInfo) { if (activeDatabase.isEmpty() || (activeDatabase.get().getDatabase() == null)) { OOError.noDataBaseIsOpenForCiting() - .setTitle(errorDialogTitle) - .showErrorDialog(dialogService); + .setTitle(errorDialogTitle) + .showErrorDialog(dialogService); return; } @@ -519,8 +614,8 @@ private void pushEntries(CitationType citationType, boolean addPageInfo) { if (entries.isEmpty()) { OOError.noEntriesSelectedForCitation() - .setTitle(errorDialogTitle) - .showErrorDialog(dialogService); + .setTitle(errorDialogTitle) + .showErrorDialog(dialogService); return; } @@ -532,13 +627,15 @@ 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()); + citationType = + citationTypeFromOptions(withText, model.citeInParProperty().getValue()); } else { // user canceled return; @@ -555,11 +652,13 @@ private void pushEntries(CitationType citationType, boolean addPageInfo) { ? 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, + ooBase.guiActionInsertEntry( + entries, bibDatabaseContext, entryTypesManager, currentStyle, @@ -591,10 +690,13 @@ 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")); + 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")); Optional databaseContext = stateManager.getActiveDatabase(); if (citePressed && databaseContext.isPresent()) { @@ -605,7 +707,8 @@ private boolean checkThatEntriesHaveKeys(List entries) { // Generate key new CitationKeyGenerator(databaseContext.get(), citationKeyPatternPreferences) .generateAndSetKey(entry) - .ifPresent(change -> undoCompound.addEdit(new UndoableKeyChange(change))); + .ifPresent( + change -> undoCompound.addEdit(new UndoableKeyChange(change))); } } undoCompound.end(); @@ -622,33 +725,51 @@ 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 alwaysAddCitedOnPagesText = new CheckMenuItem(Localization.lang("Automatically add \"Cited on pages...\" at the end of bibliographic entries")); - alwaysAddCitedOnPagesText.selectedProperty().set(openOfficePreferences.getAlwaysAddCitedOnPages()); - alwaysAddCitedOnPagesText.setOnAction(e -> openOfficePreferences.setAlwaysAddCitedOnPages(alwaysAddCitedOnPagesText.isSelected())); - - EasyBind.listen(currentStyleProperty, (obs, oldValue, newValue) -> { - switch (newValue) { - case JStyle ignored -> { - if (!contextMenu.getItems().contains(alwaysAddCitedOnPagesText)) { - contextMenu.getItems().add(1, alwaysAddCitedOnPagesText); + CheckMenuItem alwaysAddCitedOnPagesText = + new CheckMenuItem( + Localization.lang( + "Automatically add \"Cited on pages...\" at the end of bibliographic entries")); + alwaysAddCitedOnPagesText + .selectedProperty() + .set(openOfficePreferences.getAlwaysAddCitedOnPages()); + alwaysAddCitedOnPagesText.setOnAction( + e -> + openOfficePreferences.setAlwaysAddCitedOnPages( + alwaysAddCitedOnPagesText.isSelected())); + + EasyBind.listen( + currentStyleProperty, + (obs, oldValue, newValue) -> { + switch (newValue) { + case JStyle ignored -> { + if (!contextMenu.getItems().contains(alwaysAddCitedOnPagesText)) { + contextMenu.getItems().add(1, alwaysAddCitedOnPagesText); + } + } + case CitationStyle ignored -> + contextMenu.getItems().remove(alwaysAddCitedOnPagesText); + default -> {} } - } - case CitationStyle ignored -> - 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); - MenuItem clearConnectionSettings = new MenuItem(Localization.lang("Clear connection settings")); + MenuItem clearConnectionSettings = + new MenuItem(Localization.lang("Clear connection settings")); if (openOfficePreferences.getUseAllDatabases()) { useAllBases.setSelected(true); @@ -657,20 +778,25 @@ private ContextMenu createSettingsPopup() { } autoSync.setOnAction(e -> openOfficePreferences.setSyncWhenCiting(autoSync.isSelected())); - useAllBases.setOnAction(e -> openOfficePreferences.setUseAllDatabases(useAllBases.isSelected())); - useActiveBase.setOnAction(e -> openOfficePreferences.setUseAllDatabases(!useActiveBase.isSelected())); - clearConnectionSettings.setOnAction(e -> { - openOfficePreferences.clearConnectionSettings(); - dialogService.notify(Localization.lang("Cleared connection settings")); - }); - - contextMenu.getItems().addAll( - autoSync, - new SeparatorMenuItem(), - useActiveBase, - useAllBases, - new SeparatorMenuItem(), - clearConnectionSettings); + useAllBases.setOnAction( + e -> openOfficePreferences.setUseAllDatabases(useAllBases.isSelected())); + useActiveBase.setOnAction( + e -> openOfficePreferences.setUseAllDatabases(!useActiveBase.isSelected())); + clearConnectionSettings.setOnAction( + e -> { + openOfficePreferences.clearConnectionSettings(); + dialogService.notify(Localization.lang("Cleared connection settings")); + }); + + contextMenu + .getItems() + .addAll( + autoSync, + new SeparatorMenuItem(), + useActiveBase, + useAllBases, + new SeparatorMenuItem(), + clearConnectionSettings); if (currentStyle instanceof JStyle) { contextMenu.getItems().add(1, alwaysAddCitedOnPagesText); diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java index e907147c684a..2f2b50d8e4ba 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java @@ -1,8 +1,9 @@ package org.jabref.gui.openoffice; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.IntStream; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.collections.ListChangeListener; @@ -19,6 +20,7 @@ import javafx.scene.control.TableView; import javafx.scene.layout.VBox; +import org.controlsfx.control.textfield.CustomTextField; import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.preferences.GuiPreferences; @@ -42,10 +44,9 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.types.StandardEntryType; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; -import org.controlsfx.control.textfield.CustomTextField; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.IntStream; public class StyleSelectDialogView extends BaseDialog { @@ -80,23 +81,24 @@ public class StyleSelectDialogView extends BaseDialog { public StyleSelectDialogView(StyleLoader loader) { this.loader = loader; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); - - setResultConverter(button -> { - if (button == ButtonType.OK) { - viewModel.storePrefs(); - return viewModel.getSelectedStyle(); - } - return null; - }); + ViewLoader.view(this).load().setAsDialogPane(this); + + setResultConverter( + button -> { + if (button == ButtonType.OK) { + viewModel.storePrefs(); + return viewModel.getSelectedStyle(); + } + return null; + }); setTitle(Localization.lang("Style selection")); } @FXML private void initialize() { - viewModel = new StyleSelectDialogViewModel(dialogService, loader, preferences, taskExecutor, bibEntryTypesManager); + viewModel = + new StyleSelectDialogViewModel( + dialogService, loader, preferences, taskExecutor, bibEntryTypesManager); availableListView.setItems(viewModel.getAvailableLayouts()); new ViewModelListCellFactory() @@ -105,14 +107,22 @@ private void initialize() { this.setOnShown(this::onDialogShown); - availableListView.getItems().addListener((ListChangeListener) c -> { - if (c.next() && c.wasAdded() && !initialScrollPerformed.get()) { - Platform.runLater(this::scrollToCurrentStyle); - } - }); - - availableListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> - viewModel.selectedLayoutProperty().set(newValue)); + availableListView + .getItems() + .addListener( + (ListChangeListener) + c -> { + if (c.next() && c.wasAdded() && !initialScrollPerformed.get()) { + Platform.runLater(this::scrollToCurrentStyle); + } + }); + + availableListView + .getSelectionModel() + .selectedItemProperty() + .addListener( + (observable, oldValue, newValue) -> + viewModel.selectedLayoutProperty().set(newValue)); PreviewViewer cslPreviewViewer = initializePreviewViewer(TestEntry.getTestEntry()); EasyBind.subscribe(viewModel.selectedLayoutProperty(), cslPreviewViewer::setLayout); @@ -130,12 +140,13 @@ private void initialize() { colDeleteIcon.setCellValueFactory(cellData -> cellData.getValue().internalStyleProperty()); new ValueTableCellFactory() - .withGraphic(internalStyle -> { - if (!internalStyle) { - return IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode(); - } - return null; - }) + .withGraphic( + internalStyle -> { + if (!internalStyle) { + return IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode(); + } + return null; + }) .withOnMouseClickedEvent(item -> evt -> viewModel.deleteStyle()) .withTooltip(item -> Localization.lang("Remove style")) .install(colDeleteIcon); @@ -143,50 +154,72 @@ private void initialize() { edit.setOnAction(e -> viewModel.editStyle()); new ViewModelTableRowFactory() - .withOnMouseClickedEvent((item, event) -> { - if (event.getClickCount() == 2) { - viewModel.viewStyle(item); - } - }) + .withOnMouseClickedEvent( + (item, event) -> { + if (event.getClickCount() == 2) { + viewModel.viewStyle(item); + } + }) .withContextMenu(item -> createContextMenu()) .install(tvStyles); - tvStyles.getSelectionModel().selectedItemProperty().addListener((observable, oldvalue, newvalue) -> { - if (newvalue == null) { - viewModel.selectedItemProperty().setValue(oldvalue); - } else { - viewModel.selectedItemProperty().setValue(newvalue); - } - }); + tvStyles.getSelectionModel() + .selectedItemProperty() + .addListener( + (observable, oldvalue, newvalue) -> { + if (newvalue == null) { + viewModel.selectedItemProperty().setValue(oldvalue); + } else { + viewModel.selectedItemProperty().setValue(newvalue); + } + }); tvStyles.setItems(viewModel.stylesProperty()); add.setGraphic(IconTheme.JabRefIcons.ADD.getGraphicNode()); - EasyBind.subscribe(viewModel.selectedItemProperty(), style -> { - if (viewModel.getSelectedStyle() instanceof JStyle) { - tvStyles.getSelectionModel().select(style); - previewArticle.setLayout(new TextBasedPreviewLayout(style.getJStyle().getReferenceFormat(StandardEntryType.Article))); - previewBook.setLayout(new TextBasedPreviewLayout(style.getJStyle().getReferenceFormat(StandardEntryType.Book))); - } - }); + EasyBind.subscribe( + viewModel.selectedItemProperty(), + style -> { + if (viewModel.getSelectedStyle() instanceof JStyle) { + tvStyles.getSelectionModel().select(style); + previewArticle.setLayout( + new TextBasedPreviewLayout( + style.getJStyle() + .getReferenceFormat(StandardEntryType.Article))); + previewBook.setLayout( + new TextBasedPreviewLayout( + style.getJStyle() + .getReferenceFormat(StandardEntryType.Book))); + } + }); availableListView.setItems(viewModel.getAvailableLayouts()); - searchBox.textProperty().addListener((observable, oldValue, newValue) -> - viewModel.setAvailableLayoutsFilter(newValue)); + searchBox + .textProperty() + .addListener( + (observable, oldValue, newValue) -> + viewModel.setAvailableLayoutsFilter(newValue)); viewModel.setSelectedTab(tabPane.getSelectionModel().getSelectedItem()); - tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - viewModel.setSelectedTab(newValue); - }); - - availableListView.setOnMouseClicked(event -> { - if (event.getClickCount() == 2) { - viewModel.handleCslStyleSelection(); // Only CSL styles can be selected with a double click, JStyles show a style description instead - this.setResult(viewModel.getSelectedStyle()); - this.close(); - } - }); + tabPane.getSelectionModel() + .selectedItemProperty() + .addListener( + (observable, oldValue, newValue) -> { + viewModel.setSelectedTab(newValue); + }); + + availableListView.setOnMouseClicked( + event -> { + if (event.getClickCount() == 2) { + viewModel + .handleCslStyleSelection(); // Only CSL styles can be selected with + // a double click, JStyles show a style + // description instead + this.setResult(viewModel.getSelectedStyle()); + this.close(); + } + }); OOStyle currentStyle = preferences.getOpenOfficePreferences().getCurrentStyle(); if (currentStyle instanceof JStyle) { @@ -196,9 +229,12 @@ private void initialize() { } viewModel.setSelectedTab(tabPane.getSelectionModel().getSelectedItem()); - tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - viewModel.setSelectedTab(newValue); - }); + tabPane.getSelectionModel() + .selectedItemProperty() + .addListener( + (observable, oldValue, newValue) -> { + viewModel.setSelectedTab(newValue); + }); updateCurrentStyleLabel(); } @@ -215,7 +251,13 @@ private void addStyleFile() { } private PreviewViewer initializePreviewViewer(BibEntry entry) { - PreviewViewer viewer = new PreviewViewer(new BibDatabaseContext(), dialogService, preferences, themeManager, taskExecutor); + PreviewViewer viewer = + new PreviewViewer( + new BibDatabaseContext(), + dialogService, + preferences, + themeManager, + taskExecutor); viewer.setEntry(entry); return viewer; } @@ -242,19 +284,23 @@ private void scrollToCurrentStyle() { OOStyle currentStyle = preferences.getOpenOfficePreferences().getCurrentStyle(); if (currentStyle instanceof CitationStyle currentCitationStyle) { - findIndexOfCurrentStyle(currentCitationStyle).ifPresent(index -> { - int itemsPerPage = calculateItemsPerPage(); - int totalItems = availableListView.getItems().size(); - int scrollToIndex = Math.max(0, Math.min(index, totalItems - itemsPerPage)); - - availableListView.scrollTo(scrollToIndex); - availableListView.getSelectionModel().select(index); - - Platform.runLater(() -> { - availableListView.scrollTo(Math.max(0, index - 1)); - availableListView.scrollTo(index); - }); - }); + findIndexOfCurrentStyle(currentCitationStyle) + .ifPresent( + index -> { + int itemsPerPage = calculateItemsPerPage(); + int totalItems = availableListView.getItems().size(); + int scrollToIndex = + Math.max(0, Math.min(index, totalItems - itemsPerPage)); + + availableListView.scrollTo(scrollToIndex); + availableListView.getSelectionModel().select(index); + + Platform.runLater( + () -> { + availableListView.scrollTo(Math.max(0, index - 1)); + availableListView.scrollTo(index); + }); + }); } } @@ -265,8 +311,14 @@ private int calculateItemsPerPage() { private Optional findIndexOfCurrentStyle(CitationStyle currentStyle) { return IntStream.range(0, availableListView.getItems().size()) - .boxed() - .filter(i -> availableListView.getItems().get(i).getFilePath().equals(currentStyle.getFilePath())) - .findFirst(); + .boxed() + .filter( + i -> + availableListView + .getItems() + .get(i) + .getFilePath() + .equals(currentStyle.getFilePath())) + .findFirst(); } } diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java index 82befda6b059..11f31075af3c 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java @@ -1,11 +1,5 @@ package org.jabref.gui.openoffice; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleListProperty; @@ -40,6 +34,12 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + public class StyleSelectDialogViewModel { private final DialogService dialogService; @@ -47,18 +47,24 @@ public class StyleSelectDialogViewModel { private final ExternalApplicationsPreferences externalApplicationsPreferences; private final FilePreferences filePreferences; private final OpenOfficePreferences openOfficePreferences; - private final ListProperty styles = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty selectedItem = new SimpleObjectProperty<>(); - private final ObservableList availableLayouts = FXCollections.observableArrayList(); - private final ObjectProperty selectedLayoutProperty = new SimpleObjectProperty<>(); - private final FilteredList filteredAvailableLayouts = new FilteredList<>(availableLayouts); + private final ListProperty styles = + new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ObjectProperty selectedItem = + new SimpleObjectProperty<>(); + private final ObservableList availableLayouts = + FXCollections.observableArrayList(); + private final ObjectProperty selectedLayoutProperty = + new SimpleObjectProperty<>(); + private final FilteredList filteredAvailableLayouts = + new FilteredList<>(availableLayouts); private final ObjectProperty selectedTab = new SimpleObjectProperty<>(); - public StyleSelectDialogViewModel(DialogService dialogService, - StyleLoader styleLoader, - GuiPreferences preferences, - TaskExecutor taskExecutor, - BibEntryTypesManager bibEntryTypesManager) { + public StyleSelectDialogViewModel( + DialogService dialogService, + StyleLoader styleLoader, + GuiPreferences preferences, + TaskExecutor taskExecutor, + BibEntryTypesManager bibEntryTypesManager) { this.dialogService = dialogService; this.externalApplicationsPreferences = preferences.getExternalApplicationsPreferences(); this.filePreferences = preferences.getFilePreferences(); @@ -74,22 +80,43 @@ public StyleSelectDialogViewModel(DialogService dialogService, } BackgroundTask.wrap(CitationStyle::discoverCitationStyles) - .onSuccess(styles -> { - List layouts = styles.stream() - .map(style -> new CitationStylePreviewLayout(style, bibEntryTypesManager)) - .collect(Collectors.toList()); - availableLayouts.setAll(layouts); - - if (currentStyle instanceof CitationStyle citationStyle) { - selectedLayoutProperty.set(availableLayouts.stream().filter(csl -> csl.getFilePath().equals(citationStyle.getFilePath())).findFirst().orElse(availableLayouts.getFirst())); - } - }) - .onFailure(ex -> dialogService.showErrorDialogAndWait("Error discovering citation styles", ex)) - .executeWith(taskExecutor); + .onSuccess( + styles -> { + List layouts = + styles.stream() + .map( + style -> + new CitationStylePreviewLayout( + style, bibEntryTypesManager)) + .collect(Collectors.toList()); + availableLayouts.setAll(layouts); + + if (currentStyle instanceof CitationStyle citationStyle) { + selectedLayoutProperty.set( + availableLayouts.stream() + .filter( + csl -> + csl.getFilePath() + .equals( + citationStyle + .getFilePath())) + .findFirst() + .orElse(availableLayouts.getFirst())); + } + }) + .onFailure( + ex -> + dialogService.showErrorDialogAndWait( + "Error discovering citation styles", ex)) + .executeWith(taskExecutor); } public StyleSelectItemViewModel fromOOBibStyle(JStyle style) { - return new StyleSelectItemViewModel(style.getName(), String.join(", ", style.getJournals()), style.isInternalStyle() ? Localization.lang("Internal style") : style.getPath(), style); + return new StyleSelectItemViewModel( + style.getName(), + String.join(", ", style.getJournals()), + style.isInternalStyle() ? Localization.lang("Internal style") : style.getPath(), + style); } public JStyle toJStyle(StyleSelectItemViewModel item) { @@ -97,25 +124,36 @@ public JStyle toJStyle(StyleSelectItemViewModel item) { } public void addStyleFile() { - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("Style file"), StandardFileType.JSTYLE) - .withDefaultExtension(Localization.lang("Style file"), StandardFileType.JSTYLE) - .withInitialDirectory(filePreferences.getWorkingDirectory()) - .build(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .addExtensionFilter( + Localization.lang("Style file"), StandardFileType.JSTYLE) + .withDefaultExtension( + Localization.lang("Style file"), StandardFileType.JSTYLE) + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); Optional path = dialogService.showFileOpenDialog(fileDialogConfiguration); - path.map(Path::toAbsolutePath).map(Path::toString).ifPresent(stylePath -> { - if (styleLoader.addStyleIfValid(stylePath)) { - openOfficePreferences.setCurrentJStyle(stylePath); - styles.setAll(loadStyles()); - selectedItem.setValue(getStyleOrDefault(stylePath)); - } 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\".")); - } - }); + path.map(Path::toAbsolutePath) + .map(Path::toString) + .ifPresent( + stylePath -> { + if (styleLoader.addStyleIfValid(stylePath)) { + openOfficePreferences.setCurrentJStyle(stylePath); + styles.setAll(loadStyles()); + selectedItem.setValue(getStyleOrDefault(stylePath)); + } 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 List loadStyles() { - return styleLoader.getStyles().stream().map(this::fromOOBibStyle).collect(Collectors.toList()); + return styleLoader.getStyles().stream() + .map(this::fromOOBibStyle) + .collect(Collectors.toList()); } public ListProperty stylesProperty() { @@ -135,11 +173,17 @@ public ObjectProperty selectedItemProperty() { public void editStyle() { JStyle jStyle = selectedItem.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); } } @@ -156,11 +200,12 @@ public void viewStyle(StyleSelectItemViewModel item) { } public void storePrefs() { - List externalStyles = styles.stream() - .map(this::toJStyle) - .filter(style -> !style.isInternalStyle()) - .map(JStyle::getPath) - .collect(Collectors.toList()); + List externalStyles = + styles.stream() + .map(this::toJStyle) + .filter(style -> !style.isInternalStyle()) + .map(JStyle::getPath) + .collect(Collectors.toList()); openOfficePreferences.setExternalStyles(externalStyles); @@ -174,7 +219,10 @@ public void storePrefs() { } private StyleSelectItemViewModel getStyleOrDefault(String stylePath) { - return styles.stream().filter(style -> style.getStylePath().equals(stylePath)).findFirst().orElse(styles.getFirst()); + return styles.stream() + .filter(style -> style.getStylePath().equals(stylePath)) + .findFirst() + .orElse(styles.getFirst()); } public ObservableList getAvailableLayouts() { @@ -186,8 +234,12 @@ public ObjectProperty selectedLayoutProperty() { } public void setAvailableLayoutsFilter(String searchTerm) { - filteredAvailableLayouts.setPredicate(layout -> - searchTerm.isEmpty() || layout.getDisplayName().toLowerCase().contains(searchTerm.toLowerCase())); + filteredAvailableLayouts.setPredicate( + layout -> + searchTerm.isEmpty() + || layout.getDisplayName() + .toLowerCase() + .contains(searchTerm.toLowerCase())); } public Tab getSelectedTab() { diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java index a562f645bba3..3feaaed417bc 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java @@ -16,7 +16,8 @@ public class StyleSelectItemViewModel { 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(); diff --git a/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java b/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java index 0cca01f40ced..109df48a8a72 100644 --- a/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java +++ b/src/main/java/org/jabref/gui/preferences/AbstractPreferenceTabView.java @@ -1,6 +1,6 @@ package org.jabref.gui.preferences; -import java.util.List; +import jakarta.inject.Inject; import javafx.scene.Node; import javafx.scene.layout.VBox; @@ -8,9 +8,10 @@ import org.jabref.gui.DialogService; import org.jabref.logic.util.TaskExecutor; -import jakarta.inject.Inject; +import java.util.List; -public abstract class AbstractPreferenceTabView extends VBox implements PreferencesTab { +public abstract class AbstractPreferenceTabView extends VBox + implements PreferencesTab { @Inject protected TaskExecutor taskExecutor; @Inject protected DialogService dialogService; diff --git a/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java b/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java index d3cfb4de424a..3f1a16618a95 100644 --- a/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java +++ b/src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java @@ -1,17 +1,7 @@ package org.jabref.gui.preferences; -import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.SequencedMap; -import java.util.Set; -import java.util.stream.Collectors; +import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; @@ -69,12 +59,22 @@ import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; import org.jabref.model.strings.StringUtil; - -import com.airhacks.afterburner.injection.Injector; -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SequencedMap; +import java.util.Set; +import java.util.stream.Collectors; + public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPreferences { // Public because needed for pref migration @@ -114,7 +114,8 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre 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_PREFERRED_POSITIONS = + "sidePaneComponentPreferredPositions"; private static final String SIDE_PANE_COMPONENT_NAMES = "sidePaneComponentNames"; // endregion @@ -153,7 +154,8 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre 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 USE_DEFAULT_FILE_BROWSER_APPLICATION = + "userDefaultFileBrowserApplication"; private static final String CITE_COMMAND = "citeCommand"; private static final String EMAIL_SUBJECT = "emailSubject"; private static final String KINDLE_EMAIL = "kindleEmail"; @@ -208,8 +210,10 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre private static final String ID_ENTRY_GENERATOR = "idEntryGenerator"; 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_EXTENSION = + "unlinkedFilesSelectedExtension"; + private static final String UNLINKED_FILES_SELECTED_DATE_RANGE = + "unlinkedFilesSelectedDateRange"; private static final String UNLINKED_FILES_SELECTED_SORT = "unlinkedFilesSelectedSort"; private static JabRefGuiPreferences singleton; @@ -235,7 +239,8 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre private JabRefGuiPreferences() { super(); - defaults.put(JOURNAL_POPUP, EntryEditorPreferences.JournalPopupEnabled.FIRST_START.toString()); + defaults.put( + JOURNAL_POPUP, EntryEditorPreferences.JournalPopupEnabled.FIRST_START.toString()); defaults.put(ENTRY_EDITOR_HEIGHT, 0.65); defaults.put(ENTRY_EDITOR_PREVIEW_DIVIDER_POS, 0.5); @@ -247,15 +252,23 @@ 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 @@ -289,7 +302,9 @@ private JabRefGuiPreferences() { defaults.put(USE_DEFAULT_CONSOLE_APPLICATION, Boolean.TRUE); defaults.put(USE_DEFAULT_FILE_BROWSER_APPLICATION, Boolean.TRUE); if (OS.WINDOWS) { - defaults.put(CONSOLE_COMMAND, "C:\\Program Files\\ConEmu\\ConEmu64.exe /single /dir \"%DIR\""); + defaults.put( + CONSOLE_COMMAND, + "C:\\Program Files\\ConEmu\\ConEmu64.exe /single /dir \"%DIR\""); defaults.put(FILE_BROWSER_COMMAND, "explorer.exe /select, \"%DIR\""); } else { defaults.put(CONSOLE_COMMAND, ""); @@ -320,25 +335,26 @@ 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 @@ -373,7 +389,9 @@ private JabRefGuiPreferences() { // region: Main table, main table column, and search dialog column preferences defaults.put(EXTRA_FILE_COLUMNS, Boolean.FALSE); - defaults.put(COLUMN_NAMES, "search_score;groups;group_icons;files;linked_id;field:entrytype;field:author/editor;field:title;field:year;field:journal/booktitle;special:ranking;special:readstatus;special:priority"); + defaults.put( + COLUMN_NAMES, + "search_score;groups;group_icons;files;linked_id;field:entrytype;field:author/editor;field:title;field:year;field:journal/booktitle;special:ranking;special:readstatus;special:priority"); defaults.put(COLUMN_WIDTHS, "50;28;40;28;28;75;300;470;60;130;50;50;50"); defaults.put(SIDE_PANE_COMPONENT_NAMES, ""); @@ -408,40 +426,73 @@ 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(DEFAULT_SHOW_SOURCE), - getBoolean(VALIDATE_IN_ENTRY_EDITOR), - getBoolean(ALLOW_INTEGER_EDITION_BIBTEX), - getDouble(ENTRY_EDITOR_HEIGHT), - 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(), (obs, oldValue, newValue) -> storeEntryEditorTabs(newValue)); + 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(DEFAULT_SHOW_SOURCE), + getBoolean(VALIDATE_IN_ENTRY_EDITOR), + getBoolean(ALLOW_INTEGER_EDITION_BIBTEX), + getDouble(ENTRY_EDITOR_HEIGHT), + 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(), + (obs, oldValue, newValue) -> storeEntryEditorTabs(newValue)); // defaultEntryEditorTabs are read-only - EasyBind.listen(entryEditorPreferences.shouldOpenOnNewEntryProperty(), (obs, oldValue, newValue) -> putBoolean(AUTO_OPEN_FORM, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowRecommendationsTabProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_RECOMMENDATIONS, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowAiSummaryTabProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_AI_SUMMARY, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowAiChatTabProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_AI_CHAT, newValue)); - EasyBind.listen(entryEditorPreferences.shouldShowLatexCitationsTabProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_LATEX_CITATIONS, newValue)); - EasyBind.listen(entryEditorPreferences.showSourceTabByDefaultProperty(), (obs, oldValue, newValue) -> putBoolean(DEFAULT_SHOW_SOURCE, newValue)); - EasyBind.listen(entryEditorPreferences.enableValidationProperty(), (obs, oldValue, newValue) -> putBoolean(VALIDATE_IN_ENTRY_EDITOR, newValue)); - EasyBind.listen(entryEditorPreferences.allowIntegerEditionBibtexProperty(), (obs, oldValue, newValue) -> putBoolean(ALLOW_INTEGER_EDITION_BIBTEX, newValue)); - EasyBind.listen(entryEditorPreferences.dividerPositionProperty(), (obs, oldValue, newValue) -> putDouble(ENTRY_EDITOR_HEIGHT, newValue.doubleValue())); - EasyBind.listen(entryEditorPreferences.autoLinkEnabledProperty(), (obs, oldValue, newValue) -> putBoolean(AUTOLINK_FILES_ENABLED, newValue)); - EasyBind.listen(entryEditorPreferences.enableJournalPopupProperty(), (obs, oldValue, newValue) -> put(JOURNAL_POPUP, newValue.toString())); - EasyBind.listen(entryEditorPreferences.shouldShowLSciteTabProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_SCITE_TAB, newValue)); - EasyBind.listen(entryEditorPreferences.showUserCommentsFieldsProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_USER_COMMENTS_FIELDS, newValue)); - EasyBind.listen(entryEditorPreferences.previewWidthDividerPositionProperty(), (obs, oldValue, newValue) -> putDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS, newValue.doubleValue())); + EasyBind.listen( + entryEditorPreferences.shouldOpenOnNewEntryProperty(), + (obs, oldValue, newValue) -> putBoolean(AUTO_OPEN_FORM, newValue)); + EasyBind.listen( + entryEditorPreferences.shouldShowRecommendationsTabProperty(), + (obs, oldValue, newValue) -> putBoolean(SHOW_RECOMMENDATIONS, newValue)); + EasyBind.listen( + entryEditorPreferences.shouldShowAiSummaryTabProperty(), + (obs, oldValue, newValue) -> putBoolean(SHOW_AI_SUMMARY, newValue)); + EasyBind.listen( + entryEditorPreferences.shouldShowAiChatTabProperty(), + (obs, oldValue, newValue) -> putBoolean(SHOW_AI_CHAT, newValue)); + EasyBind.listen( + entryEditorPreferences.shouldShowLatexCitationsTabProperty(), + (obs, oldValue, newValue) -> putBoolean(SHOW_LATEX_CITATIONS, newValue)); + EasyBind.listen( + entryEditorPreferences.showSourceTabByDefaultProperty(), + (obs, oldValue, newValue) -> putBoolean(DEFAULT_SHOW_SOURCE, newValue)); + EasyBind.listen( + entryEditorPreferences.enableValidationProperty(), + (obs, oldValue, newValue) -> putBoolean(VALIDATE_IN_ENTRY_EDITOR, newValue)); + EasyBind.listen( + entryEditorPreferences.allowIntegerEditionBibtexProperty(), + (obs, oldValue, newValue) -> putBoolean(ALLOW_INTEGER_EDITION_BIBTEX, newValue)); + EasyBind.listen( + entryEditorPreferences.dividerPositionProperty(), + (obs, oldValue, newValue) -> + putDouble(ENTRY_EDITOR_HEIGHT, newValue.doubleValue())); + EasyBind.listen( + entryEditorPreferences.autoLinkEnabledProperty(), + (obs, oldValue, newValue) -> putBoolean(AUTOLINK_FILES_ENABLED, newValue)); + EasyBind.listen( + entryEditorPreferences.enableJournalPopupProperty(), + (obs, oldValue, newValue) -> put(JOURNAL_POPUP, newValue.toString())); + EasyBind.listen( + entryEditorPreferences.shouldShowLSciteTabProperty(), + (obs, oldValue, newValue) -> putBoolean(SHOW_SCITE_TAB, newValue)); + EasyBind.listen( + entryEditorPreferences.showUserCommentsFieldsProperty(), + (obs, oldValue, newValue) -> putBoolean(SHOW_USER_COMMENTS_FIELDS, newValue)); + EasyBind.listen( + entryEditorPreferences.previewWidthDividerPositionProperty(), + (obs, oldValue, newValue) -> + putDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS, newValue.doubleValue())); return entryEditorPreferences; } @@ -474,11 +525,16 @@ private Map> getEntryEditorTabs() { */ 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]); @@ -504,11 +560,15 @@ 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; } + // endregion @Override @@ -517,23 +577,40 @@ 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; } @@ -551,31 +628,47 @@ public AutoCompletePreferences getAutoCompletePreferences() { 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()))); - 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) { - putBoolean(AUTOCOMPLETER_LAST_FIRST, true); - putBoolean(AUTOCOMPLETER_FIRST_LAST, false); - } else { - putBoolean(AUTOCOMPLETER_LAST_FIRST, false); - putBoolean(AUTOCOMPLETER_FIRST_LAST, true); - } - }); + 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) { + putBoolean(AUTOCOMPLETER_LAST_FIRST, true); + putBoolean(AUTOCOMPLETER_FIRST_LAST, false); + } else { + putBoolean(AUTOCOMPLETER_LAST_FIRST, false); + putBoolean(AUTOCOMPLETER_FIRST_LAST, true); + } + }); return autoCompletePreferences; } @@ -586,23 +679,38 @@ 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), - get(ID_ENTRY_GENERATOR), - getDouble(SIDE_PANE_WIDTH)); - - EasyBind.listen(coreGuiPreferences.positionXProperty(), (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_POS_X, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.positionYProperty(), (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_POS_Y, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.sizeXProperty(), (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_WIDTH, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.sizeYProperty(), (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_HEIGHT, newValue.doubleValue())); - EasyBind.listen(coreGuiPreferences.windowMaximisedProperty(), (obs, oldValue, newValue) -> putBoolean(WINDOW_MAXIMISED, newValue)); - EasyBind.listen(coreGuiPreferences.sidePaneWidthProperty(), (obs, oldValue, newValue) -> putDouble(SIDE_PANE_WIDTH, newValue.doubleValue())); - - EasyBind.listen(coreGuiPreferences.lastSelectedIdBasedFetcherProperty(), (obs, oldValue, newValue) -> put(ID_ENTRY_GENERATOR, newValue)); + coreGuiPreferences = + new CoreGuiPreferences( + getDouble(MAIN_WINDOW_POS_X), + getDouble(MAIN_WINDOW_POS_Y), + getDouble(MAIN_WINDOW_WIDTH), + getDouble(MAIN_WINDOW_HEIGHT), + getBoolean(WINDOW_MAXIMISED), + get(ID_ENTRY_GENERATOR), + getDouble(SIDE_PANE_WIDTH)); + + EasyBind.listen( + coreGuiPreferences.positionXProperty(), + (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_POS_X, newValue.doubleValue())); + EasyBind.listen( + coreGuiPreferences.positionYProperty(), + (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_POS_Y, newValue.doubleValue())); + EasyBind.listen( + coreGuiPreferences.sizeXProperty(), + (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_WIDTH, newValue.doubleValue())); + EasyBind.listen( + coreGuiPreferences.sizeYProperty(), + (obs, oldValue, newValue) -> putDouble(MAIN_WINDOW_HEIGHT, newValue.doubleValue())); + EasyBind.listen( + coreGuiPreferences.windowMaximisedProperty(), + (obs, oldValue, newValue) -> putBoolean(WINDOW_MAXIMISED, newValue)); + EasyBind.listen( + coreGuiPreferences.sidePaneWidthProperty(), + (obs, oldValue, newValue) -> putDouble(SIDE_PANE_WIDTH, newValue.doubleValue())); + + EasyBind.listen( + coreGuiPreferences.lastSelectedIdBasedFetcherProperty(), + (obs, oldValue, newValue) -> put(ID_ENTRY_GENERATOR, newValue)); return coreGuiPreferences; } @@ -615,37 +723,63 @@ 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), - getStringList(SELECTED_SLR_CATALOGS)); - - EasyBind.listen(workspacePreferences.languageProperty(), (obs, oldValue, newValue) -> { - put(LANGUAGE, newValue.getId()); - if (oldValue != newValue) { - setLanguageDependentDefaultValues(); - Localization.setLanguage(newValue); - } - }); - - 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)); - workspacePreferences.getSelectedSlrCatalogs().addListener((ListChangeListener) change -> - putStringList(SELECTED_SLR_CATALOGS, workspacePreferences.getSelectedSlrCatalogs())); + 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), + getStringList(SELECTED_SLR_CATALOGS)); + + EasyBind.listen( + workspacePreferences.languageProperty(), + (obs, oldValue, newValue) -> { + put(LANGUAGE, newValue.getId()); + if (oldValue != newValue) { + setLanguageDependentDefaultValues(); + Localization.setLanguage(newValue); + } + }); + + 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)); + workspacePreferences + .getSelectedSlrCatalogs() + .addListener( + (ListChangeListener) + change -> + putStringList( + SELECTED_SLR_CATALOGS, + workspacePreferences.getSelectedSlrCatalogs())); return workspacePreferences; } @@ -655,15 +789,22 @@ public UnlinkedFilesDialogPreferences getUnlinkedFilesDialogPreferences() { return unlinkedFilesDialogPreferences; } - unlinkedFilesDialogPreferences = new UnlinkedFilesDialogPreferences( - get(UNLINKED_FILES_SELECTED_EXTENSION), - DateRange.parse(get(UNLINKED_FILES_SELECTED_DATE_RANGE)), - 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())); + unlinkedFilesDialogPreferences = + new UnlinkedFilesDialogPreferences( + get(UNLINKED_FILES_SELECTED_EXTENSION), + DateRange.parse(get(UNLINKED_FILES_SELECTED_DATE_RANGE)), + 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())); return unlinkedFilesDialogPreferences; } @@ -675,16 +816,28 @@ public SidePanePreferences getSidePanePreferences() { return sidePanePreferences; } - 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 = + 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)); return sidePanePreferences; } @@ -732,17 +885,20 @@ 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); } + // endregion @Override @@ -751,35 +907,54 @@ public ExternalApplicationsPreferences getExternalApplicationsPreferences() { return externalApplicationsPreferences; } - externalApplicationsPreferences = new ExternalApplicationsPreferences( - get(EMAIL_SUBJECT), - getBoolean(OPEN_FOLDERS_OF_ATTACHED_FILES), - CitationCommandString.from(get(CITE_COMMAND)), - CitationCommandString.from((String) defaults.get(CITE_COMMAND)), - 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)); - - EasyBind.listen(externalApplicationsPreferences.eMailSubjectProperty(), + externalApplicationsPreferences = + new ExternalApplicationsPreferences( + get(EMAIL_SUBJECT), + getBoolean(OPEN_FOLDERS_OF_ATTACHED_FILES), + CitationCommandString.from(get(CITE_COMMAND)), + CitationCommandString.from((String) defaults.get(CITE_COMMAND)), + 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)); + + EasyBind.listen( + externalApplicationsPreferences.eMailSubjectProperty(), (obs, oldValue, newValue) -> put(EMAIL_SUBJECT, newValue)); - EasyBind.listen(externalApplicationsPreferences.autoOpenEmailAttachmentsFolderProperty(), + EasyBind.listen( + externalApplicationsPreferences.autoOpenEmailAttachmentsFolderProperty(), (obs, oldValue, newValue) -> putBoolean(OPEN_FOLDERS_OF_ATTACHED_FILES, newValue)); - EasyBind.listen(externalApplicationsPreferences.citeCommandProperty(), + EasyBind.listen( + externalApplicationsPreferences.citeCommandProperty(), (obs, oldValue, newValue) -> put(CITE_COMMAND, newValue.toString())); - 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()))); - EasyBind.listen(externalApplicationsPreferences.customTerminalCommandProperty(), + 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()))); + 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 ! - EasyBind.listen(externalApplicationsPreferences.customFileBrowserCommandProperty(), + EasyBind.listen( + externalApplicationsPreferences.useCustomFileBrowserProperty(), + (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(), + EasyBind.listen( + externalApplicationsPreferences.kindleEmailProperty(), (obs, oldValue, newValue) -> put(KINDLE_EMAIL, newValue)); return externalApplicationsPreferences; @@ -790,23 +965,45 @@ 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.groupViewModeProperty().addListener((SetChangeListener) change -> { - 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())); + 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_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())); return groupsPreferences; } @@ -818,7 +1015,9 @@ 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; } @@ -832,32 +1031,51 @@ public PreviewPreferences getPreviewPreferences() { String style = get(PREVIEW_STYLE); List layouts = getPreviewLayouts(style); - this.previewPreferences = new PreviewPreferences( - layouts, - getPreviewCyclePosition(layouts), - new TextBasedPreviewLayout( - style, - getLayoutFormatterPreferences(), - Injector.instantiateModelOrService(JournalAbbreviationRepository.class)), - (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())); + this.previewPreferences = + new PreviewPreferences( + layouts, + getPreviewCyclePosition(layouts), + new TextBasedPreviewLayout( + style, + getLayoutFormatterPreferences(), + Injector.instantiateModelOrService( + JournalAbbreviationRepository.class)), + (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())); 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) { @@ -869,39 +1087,54 @@ private List getPreviewLayouts(String style) { } return cycle.stream() - .map(layout -> { - if (CitationStyle.isCitationStyleFile(layout)) { - BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); - return CitationStyle.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()); + .map( + layout -> { + if (CitationStyle.isCitationStyleFile(layout)) { + BibEntryTypesManager entryTypesManager = + Injector.instantiateModelOrService( + BibEntryTypesManager.class); + return CitationStyle.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(); - } - }).collect(Collectors.toList()) - ); + putStringList( + CYCLE_PREVIEW, + previewCycle.stream() + .map( + layout -> { + if (layout + instanceof + CitationStylePreviewLayout citationStyleLayout) { + return citationStyleLayout.getFilePath(); + } else { + return layout.getDisplayName(); + } + }) + .collect(Collectors.toList())); } private int getPreviewCyclePosition(List layouts) { @@ -912,6 +1145,7 @@ private int getPreviewCyclePosition(List layouts) { return 0; // fallback if stored position is no longer valid } } + // endregion // region PushToApplicationPreferences @@ -922,52 +1156,57 @@ public PushToApplicationPreferences getPushToApplicationPreferences() { } Map applicationCommands = new HashMap<>(); - // getEmptyIsDefault is used to ensure that an installation of a tool leads to the new path (instead of leaving the empty one) - // Reason: empty string is returned by org.jabref.gui.desktop.os.Windows.detectProgramPath if program is not found. That path is stored in the preferences. + // getEmptyIsDefault is used to ensure that an installation of a tool leads to the new path + // (instead of leaving the empty one) + // Reason: empty string is returned by org.jabref.gui.desktop.os.Windows.detectProgramPath + // if program is not found. That path is stored in the preferences. applicationCommands.put(PushToApplications.EMACS, getEmptyIsDefault(PUSH_EMACS_PATH)); applicationCommands.put(PushToApplications.LYX, getEmptyIsDefault(PUSH_LYXPIPE)); applicationCommands.put(PushToApplications.TEXMAKER, getEmptyIsDefault(PUSH_TEXMAKER_PATH)); - applicationCommands.put(PushToApplications.TEXSTUDIO, getEmptyIsDefault(PUSH_TEXSTUDIO_PATH)); + applicationCommands.put( + PushToApplications.TEXSTUDIO, getEmptyIsDefault(PUSH_TEXSTUDIO_PATH)); applicationCommands.put(PushToApplications.TEXWORKS, getEmptyIsDefault(PUSH_TEXWORKS_PATH)); applicationCommands.put(PushToApplications.VIM, getEmptyIsDefault(PUSH_VIM)); applicationCommands.put(PushToApplications.WIN_EDT, getEmptyIsDefault(PUSH_WINEDT_PATH)); - applicationCommands.put(PushToApplications.SUBLIME_TEXT, getEmptyIsDefault(PUSH_SUBLIME_TEXT_PATH)); - - pushToApplicationPreferences = new PushToApplicationPreferences( - get(PUSH_TO_APPLICATION), - applicationCommands, - get(PUSH_EMACS_ADDITIONAL_PARAMETERS), - get(PUSH_VIM_SERVER) - ); - - EasyBind.listen(pushToApplicationPreferences.activeApplicationNameProperty(), (obs, oldValue, newValue) -> put(PUSH_TO_APPLICATION, newValue)); - pushToApplicationPreferences.getCommandPaths().addListener((obs, oldValue, newValue) -> storePushToApplicationPath(newValue)); - EasyBind.listen(pushToApplicationPreferences.emacsArgumentsProperty(), (obs, oldValue, newValue) -> put(PUSH_EMACS_ADDITIONAL_PARAMETERS, newValue)); - EasyBind.listen(pushToApplicationPreferences.vimServerProperty(), (obs, oldValue, newValue) -> put(PUSH_VIM_SERVER, newValue)); + applicationCommands.put( + PushToApplications.SUBLIME_TEXT, getEmptyIsDefault(PUSH_SUBLIME_TEXT_PATH)); + + pushToApplicationPreferences = + new PushToApplicationPreferences( + get(PUSH_TO_APPLICATION), + applicationCommands, + get(PUSH_EMACS_ADDITIONAL_PARAMETERS), + get(PUSH_VIM_SERVER)); + + EasyBind.listen( + pushToApplicationPreferences.activeApplicationNameProperty(), + (obs, oldValue, newValue) -> put(PUSH_TO_APPLICATION, newValue)); + pushToApplicationPreferences + .getCommandPaths() + .addListener((obs, oldValue, newValue) -> storePushToApplicationPath(newValue)); + EasyBind.listen( + pushToApplicationPreferences.emacsArgumentsProperty(), + (obs, oldValue, newValue) -> put(PUSH_EMACS_ADDITIONAL_PARAMETERS, newValue)); + EasyBind.listen( + pushToApplicationPreferences.vimServerProperty(), + (obs, oldValue, newValue) -> put(PUSH_VIM_SERVER, newValue)); return pushToApplicationPreferences; } private void storePushToApplicationPath(Map commandPair) { - commandPair.forEach((key, value) -> { - switch (key) { - case PushToApplications.EMACS -> - put(PUSH_EMACS_PATH, value); - case PushToApplications.LYX -> - put(PUSH_LYXPIPE, value); - case PushToApplications.TEXMAKER -> - put(PUSH_TEXMAKER_PATH, value); - case PushToApplications.TEXSTUDIO -> - put(PUSH_TEXSTUDIO_PATH, value); - case PushToApplications.TEXWORKS -> - put(PUSH_TEXWORKS_PATH, value); - case PushToApplications.VIM -> - put(PUSH_VIM, value); - case PushToApplications.WIN_EDT -> - put(PUSH_WINEDT_PATH, value); - case PushToApplications.SUBLIME_TEXT -> - put(PUSH_SUBLIME_TEXT_PATH, value); - } - }); + commandPair.forEach( + (key, value) -> { + switch (key) { + case PushToApplications.EMACS -> put(PUSH_EMACS_PATH, value); + case PushToApplications.LYX -> put(PUSH_LYXPIPE, value); + case PushToApplications.TEXMAKER -> put(PUSH_TEXMAKER_PATH, value); + case PushToApplications.TEXSTUDIO -> put(PUSH_TEXSTUDIO_PATH, value); + case PushToApplications.TEXWORKS -> put(PUSH_TEXWORKS_PATH, value); + case PushToApplications.VIM -> put(PUSH_VIM, value); + case PushToApplications.WIN_EDT -> put(PUSH_WINEDT_PATH, value); + case PushToApplications.SUBLIME_TEXT -> put(PUSH_SUBLIME_TEXT_PATH, value); + } + }); } // endregion @@ -980,25 +1219,36 @@ public NameDisplayPreferences getNameDisplayPreferences() { return nameDisplayPreferences; } - nameDisplayPreferences = new NameDisplayPreferences( - getNameDisplayStyle(), - getNameAbbreviationStyle()); - - EasyBind.listen(nameDisplayPreferences.displayStyleProperty(), (obs, oldValue, newValue) -> { - putBoolean(NAMES_NATBIB, newValue == NameDisplayPreferences.DisplayStyle.NATBIB); - putBoolean(NAMES_AS_IS, newValue == NameDisplayPreferences.DisplayStyle.AS_IS); - putBoolean(NAMES_FIRST_LAST, newValue == NameDisplayPreferences.DisplayStyle.FIRSTNAME_LASTNAME); - }); - EasyBind.listen(nameDisplayPreferences.abbreviationStyleProperty(), (obs, oldValue, newValue) -> { - putBoolean(ABBR_AUTHOR_NAMES, newValue == NameDisplayPreferences.AbbreviationStyle.FULL); - putBoolean(NAMES_LAST_ONLY, newValue == NameDisplayPreferences.AbbreviationStyle.LASTNAME_ONLY); - }); + nameDisplayPreferences = + new NameDisplayPreferences(getNameDisplayStyle(), getNameAbbreviationStyle()); + + EasyBind.listen( + nameDisplayPreferences.displayStyleProperty(), + (obs, oldValue, newValue) -> { + putBoolean( + NAMES_NATBIB, newValue == NameDisplayPreferences.DisplayStyle.NATBIB); + putBoolean(NAMES_AS_IS, newValue == NameDisplayPreferences.DisplayStyle.AS_IS); + putBoolean( + NAMES_FIRST_LAST, + newValue == NameDisplayPreferences.DisplayStyle.FIRSTNAME_LASTNAME); + }); + EasyBind.listen( + nameDisplayPreferences.abbreviationStyleProperty(), + (obs, oldValue, newValue) -> { + putBoolean( + ABBR_AUTHOR_NAMES, + newValue == NameDisplayPreferences.AbbreviationStyle.FULL); + putBoolean( + NAMES_LAST_ONLY, + newValue == NameDisplayPreferences.AbbreviationStyle.LASTNAME_ONLY); + }); return nameDisplayPreferences; } private NameDisplayPreferences.AbbreviationStyle getNameAbbreviationStyle() { - NameDisplayPreferences.AbbreviationStyle abbreviationStyle = NameDisplayPreferences.AbbreviationStyle.NONE; // default + NameDisplayPreferences.AbbreviationStyle abbreviationStyle = + NameDisplayPreferences.AbbreviationStyle.NONE; // default if (getBoolean(ABBR_AUTHOR_NAMES)) { abbreviationStyle = NameDisplayPreferences.AbbreviationStyle.FULL; } else if (getBoolean(NAMES_LAST_ONLY)) { @@ -1008,7 +1258,8 @@ private NameDisplayPreferences.AbbreviationStyle getNameAbbreviationStyle() { } private NameDisplayPreferences.DisplayStyle getNameDisplayStyle() { - NameDisplayPreferences.DisplayStyle displayStyle = NameDisplayPreferences.DisplayStyle.LASTNAME_FIRSTNAME; // default + NameDisplayPreferences.DisplayStyle displayStyle = + NameDisplayPreferences.DisplayStyle.LASTNAME_FIRSTNAME; // default if (getBoolean(NAMES_NATBIB)) { displayStyle = NameDisplayPreferences.DisplayStyle.NATBIB; } else if (getBoolean(NAMES_AS_IS)) { @@ -1028,14 +1279,17 @@ public MainTablePreferences getMainTablePreferences() { return mainTablePreferences; } - mainTablePreferences = new MainTablePreferences( - getMainTableColumnPreferences(), - getBoolean(AUTO_RESIZE_MODE), - getBoolean(EXTRA_FILE_COLUMNS)); + mainTablePreferences = + new MainTablePreferences( + getMainTableColumnPreferences(), + getBoolean(AUTO_RESIZE_MODE), + getBoolean(EXTRA_FILE_COLUMNS)); - EasyBind.listen(mainTablePreferences.resizeColumnsToFitProperty(), + EasyBind.listen( + mainTablePreferences.resizeColumnsToFitProperty(), (obs, oldValue, newValue) -> putBoolean(AUTO_RESIZE_MODE, newValue)); - EasyBind.listen(mainTablePreferences.extraFileColumnsEnabledProperty(), + EasyBind.listen( + mainTablePreferences.extraFileColumnsEnabledProperty(), (obs, oldValue, newValue) -> putBoolean(EXTRA_FILE_COLUMNS, newValue)); return mainTablePreferences; @@ -1046,17 +1300,41 @@ 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); - mainTableColumnPreferences.getColumns().addListener((InvalidationListener) change -> { - putStringList(COLUMN_NAMES, getColumnNamesAsStringList(mainTableColumnPreferences)); - putStringList(COLUMN_WIDTHS, getColumnWidthsAsStringList(mainTableColumnPreferences)); - putStringList(COLUMN_SORT_TYPES, getColumnSortTypesAsStringList(mainTableColumnPreferences)); - }); - mainTableColumnPreferences.getColumnSortOrder().addListener((InvalidationListener) change -> - putStringList(COLUMN_SORT_ORDER, getColumnSortOrderAsStringList(mainTableColumnPreferences))); + mainTableColumnPreferences + .getColumns() + .addListener( + (InvalidationListener) + change -> { + putStringList( + COLUMN_NAMES, + getColumnNamesAsStringList(mainTableColumnPreferences)); + putStringList( + COLUMN_WIDTHS, + getColumnWidthsAsStringList( + mainTableColumnPreferences)); + putStringList( + COLUMN_SORT_TYPES, + getColumnSortTypesAsStringList( + mainTableColumnPreferences)); + }); + mainTableColumnPreferences + .getColumnSortOrder() + .addListener( + (InvalidationListener) + change -> + putStringList( + COLUMN_SORT_ORDER, + getColumnSortOrderAsStringList( + mainTableColumnPreferences))); return mainTableColumnPreferences; } @@ -1066,40 +1344,72 @@ public ColumnPreferences getSearchDialogColumnPreferences() { return searchDialogColumnPreferences; } - 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); + 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_WIDTHS, getColumnWidthsAsStringList(searchDialogColumnPreferences)); - putStringList(SEARCH_DIALOG_COLUMN_SORT_TYPES, getColumnSortTypesAsStringList(searchDialogColumnPreferences)); - }); - searchDialogColumnPreferences.getColumnSortOrder().addListener((InvalidationListener) change -> - putStringList(SEARCH_DIALOG_COLUMN_SORT_ORDER, getColumnSortOrderAsStringList(searchDialogColumnPreferences))); + 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_WIDTHS, + getColumnWidthsAsStringList( + searchDialogColumnPreferences)); + putStringList( + SEARCH_DIALOG_COLUMN_SORT_TYPES, + getColumnSortTypesAsStringList( + 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++) { @@ -1118,38 +1428,44 @@ 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(); + .map(column -> column.widthProperty().getValue().toString()) + .toList(); } - private static List getColumnSortTypesAsStringList(ColumnPreferences columnPreferences) { + private static List getColumnSortTypesAsStringList( + ColumnPreferences columnPreferences) { return columnPreferences.getColumns().stream() - .map(column -> column.sortTypeProperty().getValue().toString()) - .toList(); + .map(column -> column.sortTypeProperty().getValue().toString()) + .toList(); } - private static List getColumnSortOrderAsStringList(ColumnPreferences columnPreferences) { + private static List getColumnSortOrderAsStringList( + ColumnPreferences columnPreferences) { return columnPreferences.getColumnSortOrder().stream() - .map(MainTableColumnModel::getName) - .collect(Collectors.toList()); + .map(MainTableColumnModel::getName) + .collect(Collectors.toList()); } + // endregion /** @@ -1165,18 +1481,18 @@ private SelfContainedSaveOrder getSelfContainedTableSaveOrder() { @Override public SelfContainedSaveConfiguration getSelfContainedExportConfiguration() { SaveOrder exportSaveOrder = getExportSaveOrder(); - SelfContainedSaveOrder saveOrder = switch (exportSaveOrder.getOrderType()) { - case TABLE -> - this.getSelfContainedTableSaveOrder(); - case SPECIFIED -> - SelfContainedSaveOrder.of(exportSaveOrder); - case ORIGINAL -> - SaveOrder.getDefaultSaveOrder(); - }; + SelfContainedSaveOrder saveOrder = + switch (exportSaveOrder.getOrderType()) { + 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()); + saveOrder, + false, + BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, + getLibraryPreferences().shouldAlwaysReformatOnSave()); } @Override @@ -1185,12 +1501,15 @@ public KeyBindingRepository getKeyBindingRepository() { return keyBindingRepository; } - keyBindingRepository = new KeyBindingRepository(getStringList(BIND_NAMES), getStringList(BINDINGS)); + keyBindingRepository = + new KeyBindingRepository(getStringList(BIND_NAMES), getStringList(BINDINGS)); - EasyBind.listen(keyBindingRepository.getBindingsProperty(), (obs, oldValue, newValue) -> { - putStringList(BIND_NAMES, keyBindingRepository.getBindNames()); - putStringList(BINDINGS, keyBindingRepository.getBindings()); - }); + EasyBind.listen( + keyBindingRepository.getBindingsProperty(), + (obs, oldValue, newValue) -> { + putStringList(BIND_NAMES, keyBindingRepository.getBindNames()); + putStringList(BINDINGS, keyBindingRepository.getBindings()); + }); return keyBindingRepository; } diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java b/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java index bb246d0e18f8..ba5bf2ee5f30 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.java @@ -1,7 +1,9 @@ package org.jabref.gui.preferences; -import java.util.Locale; -import java.util.Optional; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.ButtonType; @@ -10,6 +12,7 @@ import javafx.scene.control.ToggleButton; import javafx.scene.input.KeyCode; +import org.controlsfx.control.textfield.CustomTextField; import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.KeyBinding; @@ -19,10 +22,8 @@ import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; -import org.controlsfx.control.textfield.CustomTextField; +import java.util.Locale; +import java.util.Optional; /** * Preferences dialog. Contains a TabbedPane, and tabs will be defined in separate classes. Tabs MUST implement the @@ -48,18 +49,18 @@ public PreferencesDialogView(Class preferencesTabToSel this.setTitle(Localization.lang("JabRef preferences")); this.preferencesTabToSelectClass = preferencesTabToSelectClass; - ViewLoader.view(this) - .load() - .setAsDialogPane(this); + ViewLoader.view(this).load().setAsDialogPane(this); - ControlHelper.setAction(saveButton, getDialogPane(), event -> savePreferencesAndCloseDialog()); + ControlHelper.setAction( + saveButton, getDialogPane(), event -> savePreferencesAndCloseDialog()); // 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(); - } - }); + searchBox.setOnKeyPressed( + event -> { + if (event.getCode() == KeyCode.ENTER) { + event.consume(); + } + }); themeManager.updateFontStyle(getDialogPane().getScene()); } @@ -75,38 +76,54 @@ private void initialize() { preferenceTabList.itemsProperty().setValue(viewModel.getPreferenceTabs()); // The list view does not respect the listener for the dialog and needs its own - preferenceTabList.setOnKeyReleased(key -> { - if (preferences.getKeyBindingRepository().checkKeyCombinationEquality(KeyBinding.CLOSE, key)) { - this.closeDialog(); - } - }); - - PreferencesSearchHandler searchHandler = new PreferencesSearchHandler(viewModel.getPreferenceTabs()); - preferenceTabList.itemsProperty().bindBidirectional(searchHandler.filteredPreferenceTabsProperty()); - searchBox.textProperty().addListener((observable, previousText, newText) -> { - searchHandler.filterTabs(newText.toLowerCase(Locale.ROOT)); - preferenceTabList.getSelectionModel().clearSelection(); - preferenceTabList.getSelectionModel().selectFirst(); - }); + preferenceTabList.setOnKeyReleased( + key -> { + if (preferences + .getKeyBindingRepository() + .checkKeyCombinationEquality(KeyBinding.CLOSE, key)) { + this.closeDialog(); + } + }); + + PreferencesSearchHandler searchHandler = + new PreferencesSearchHandler(viewModel.getPreferenceTabs()); + preferenceTabList + .itemsProperty() + .bindBidirectional(searchHandler.filteredPreferenceTabsProperty()); + searchBox + .textProperty() + .addListener( + (observable, previousText, newText) -> { + searchHandler.filterTabs(newText.toLowerCase(Locale.ROOT)); + preferenceTabList.getSelectionModel().clearSelection(); + preferenceTabList.getSelectionModel().selectFirst(); + }); searchBox.setPromptText(Localization.lang("Search") + "..."); searchBox.setLeft(IconTheme.JabRefIcons.SEARCH.getGraphicNode()); - EasyBind.subscribe(preferenceTabList.getSelectionModel().selectedItemProperty(), tab -> { - if (tab instanceof AbstractPreferenceTabView preferencesTab) { - preferencesContainer.setContent(preferencesTab.getBuilder()); - preferencesTab.prefWidthProperty().bind(preferencesContainer.widthProperty().subtract(10d)); - preferencesTab.getStyleClass().add("preferencesTab"); - } else { - preferencesContainer.setContent(null); - } - }); + EasyBind.subscribe( + preferenceTabList.getSelectionModel().selectedItemProperty(), + tab -> { + if (tab instanceof AbstractPreferenceTabView preferencesTab) { + preferencesContainer.setContent(preferencesTab.getBuilder()); + preferencesTab + .prefWidthProperty() + .bind(preferencesContainer.widthProperty().subtract(10d)); + preferencesTab.getStyleClass().add("preferencesTab"); + } 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)); + Optional tabToSelectIfExist = + preferenceTabList.getItems().stream() + .filter( + prefTab -> + prefTab.getClass().equals(preferencesTabToSelectClass)) + .findFirst(); + tabToSelectIfExist.ifPresent( + preferencesTab -> preferenceTabList.getSelectionModel().select(preferencesTab)); } else { preferenceTabList.getSelectionModel().selectFirst(); } diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java index d99eeb7fd343..f185ff0d9ea1 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesDialogViewModel.java @@ -1,8 +1,6 @@ package org.jabref.gui.preferences; -import java.util.ArrayList; -import java.util.List; -import java.util.prefs.BackingStoreException; +import com.airhacks.afterburner.injection.Injector; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyListWrapper; @@ -40,11 +38,13 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.StandardFileType; import org.jabref.model.entry.BibEntryTypesManager; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.BackingStoreException; + public class PreferencesDialogViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(PreferencesDialogViewModel.class); @@ -59,31 +59,31 @@ public PreferencesDialogViewModel(DialogService dialogService, GuiPreferences pr this.dialogService = dialogService; this.preferences = preferences; - preferenceTabs = FXCollections.observableArrayList( - new GeneralTab(), - new KeyBindingsTab(), - new GroupsTab(), - new WebSearchTab(), - new 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(), + new 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() { @@ -91,60 +91,79 @@ 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); - } - }); + 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); + } + }); } 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); - } - }); + 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); + } + }); } public void showPreferences() { - dialogService.showCustomDialogAndWait(new PreferencesFilterDialog(new PreferencesFilter(preferences))); + dialogService.showCustomDialogAndWait( + new PreferencesFilterDialog(new PreferencesFilter(preferences))); } 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")); + 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")); if (resetPreferencesConfirmed) { try { preferences.clear(); - dialogService.showWarningDialogAndWait(Localization.lang("Reset preferences"), + dialogService.showWarningDialogAndWait( + Localization.lang("Reset preferences"), Localization.lang("You must restart JabRef for this to come into effect.")); } catch (BackingStoreException ex) { LOGGER.error("Error while resetting preferences", ex); @@ -181,13 +200,16 @@ public void storeAllSettings() { preferences.flush(); if (!restartWarnings.isEmpty()) { - dialogService.showWarningDialogAndWait(Localization.lang("Restart required"), + dialogService.showWarningDialogAndWait( + Localization.lang("Restart required"), String.join(",\n", restartWarnings) + "\n\n" - + Localization.lang("You must restart JabRef for this to come into effect.")); + + Localization.lang( + "You must restart JabRef for this to come into effect.")); } - Injector.setModelOrService(BibEntryTypesManager.class, preferences.getCustomEntryTypesRepository()); + Injector.setModelOrService( + BibEntryTypesManager.class, preferences.getCustomEntryTypesRepository()); dialogService.notify(Localization.lang("Preferences recorded.")); } diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java b/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java index 8c27486f55d1..cf30225af855 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesFilter.java @@ -1,5 +1,7 @@ package org.jabref.gui.preferences; +import org.jabref.logic.preferences.CliPreferences; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -7,8 +9,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.jabref.logic.preferences.CliPreferences; - public class PreferencesFilter { private final CliPreferences preferences; @@ -22,19 +22,26 @@ public List getPreferenceOptions() { Map prefs = preferences.getPreferences(); return prefs.entrySet().stream() - .map(entry -> new PreferenceOption(entry.getKey(), entry.getValue(), defaults.get(entry.getKey()))) - .collect(Collectors.toList()); + .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 + BOOLEAN, + INTEGER, + STRING } public static class PreferenceOption implements Comparable { diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java b/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java index 60c96af5c1ec..f6a4dcb007f6 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesFilterDialog.java @@ -1,7 +1,7 @@ package org.jabref.gui.preferences; -import java.util.Locale; -import java.util.Objects; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.binding.Bindings; import javafx.beans.property.ReadOnlyObjectWrapper; @@ -19,8 +19,8 @@ import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; +import java.util.Locale; +import java.util.Objects; public class PreferencesFilterDialog extends BaseDialog { @@ -29,7 +29,11 @@ public class PreferencesFilterDialog extends BaseDialog { private final FilteredList filteredOptions; @FXML private TableView table; - @FXML private TableColumn columnType; + + @FXML + private TableColumn + columnType; + @FXML private TableColumn columnKey; @FXML private TableColumn columnValue; @FXML private TableColumn columnDefaultValue; @@ -42,9 +46,7 @@ public PreferencesFilterDialog(PreferencesFilter 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")); } @@ -52,17 +54,29 @@ public PreferencesFilterDialog(PreferencesFilter preferencesFilter) { @FXML private void initialize() { showOnlyDeviatingPreferenceOptions.setOnAction(event -> updateModel()); - filteredOptions.predicateProperty().bind(EasyBind.map(searchField.textProperty(), searchText -> { - if ((searchText == null) || searchText.isEmpty()) { - return null; - } - String lowerCaseSearchText = searchText.toLowerCase(Locale.ROOT); - return option -> option.getKey().toLowerCase(Locale.ROOT).contains(lowerCaseSearchText); - })); - columnType.setCellValueFactory(data -> new ReadOnlyObjectWrapper<>(data.getValue().getType())); + filteredOptions + .predicateProperty() + .bind( + EasyBind.map( + searchField.textProperty(), + searchText -> { + if ((searchText == null) || searchText.isEmpty()) { + return null; + } + String lowerCaseSearchText = + searchText.toLowerCase(Locale.ROOT); + return option -> + option.getKey() + .toLowerCase(Locale.ROOT) + .contains(lowerCaseSearchText); + })); + 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(""))); + columnValue.setCellValueFactory( + data -> new ReadOnlyObjectWrapper<>(data.getValue().getValue())); + columnDefaultValue.setCellValueFactory( + data -> new ReadOnlyObjectWrapper<>(data.getValue().getDefaultValue().orElse(""))); table.setItems(filteredOptions); count.textProperty().bind(Bindings.size(table.getItems()).asString("(%d)")); updateModel(); diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java b/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java index ad645c90a784..6a425549b261 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesSearchHandler.java @@ -1,8 +1,6 @@ package org.jabref.gui.preferences; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; +import com.google.common.collect.ArrayListMultimap; import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; @@ -12,7 +10,9 @@ import javafx.scene.Parent; import javafx.scene.control.Labeled; -import com.google.common.collect.ArrayListMultimap; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; class PreferencesSearchHandler { @@ -25,7 +25,8 @@ class PreferencesSearchHandler { PreferencesSearchHandler(List preferenceTabs) { this.preferenceTabs = preferenceTabs; this.preferenceTabsLabelNames = getPrefsTabLabelMap(); - this.filteredPreferenceTabs = new SimpleListProperty<>(FXCollections.observableArrayList(preferenceTabs)); + this.filteredPreferenceTabs = + new SimpleListProperty<>(FXCollections.observableArrayList(preferenceTabs)); } public void filterTabs(String text) { @@ -44,7 +45,8 @@ public void filterTabs(String text) { highlightLabel(labeled); } } - boolean tabNameIsMatchedByQuery = tab.getTabName().toLowerCase(Locale.ROOT).contains(text); + boolean tabNameIsMatchedByQuery = + tab.getTabName().toLowerCase(Locale.ROOT).contains(text); if (tabContainsLabel || tabNameIsMatchedByQuery) { filteredPreferenceTabs.add(tab); } @@ -61,7 +63,8 @@ private void highlightLabel(Labeled labeled) { } private void clearHighlights() { - highlightedLabels.forEach(labeled -> labeled.pseudoClassStateChanged(labelHighlight, false)); + highlightedLabels.forEach( + labeled -> labeled.pseudoClassStateChanged(labelHighlight, false)); } private void clearSearch() { @@ -87,7 +90,10 @@ protected ListProperty filteredPreferenceTabsProperty() { return filteredPreferenceTabs; } - private static void scanLabeledControls(Parent parent, ArrayListMultimap prefsTabLabelMap, PreferencesTab preferencesTab) { + private static void scanLabeledControls( + Parent parent, + ArrayListMultimap prefsTabLabelMap, + PreferencesTab preferencesTab) { for (Node child : parent.getChildrenUnmodifiable()) { if (child instanceof Labeled labeled) { if (!labeled.getText().isEmpty()) { diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesTab.java b/src/main/java/org/jabref/gui/preferences/PreferencesTab.java index d038e49622fe..220dd57f9dd3 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesTab.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesTab.java @@ -1,9 +1,9 @@ package org.jabref.gui.preferences; -import java.util.List; - import javafx.scene.Node; +import java.util.List; + /** * A prefsTab is a component displayed in the PreferenceDialog. *

    diff --git a/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java b/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java index 0ba021be858b..14f1036772b7 100644 --- a/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java +++ b/src/main/java/org/jabref/gui/preferences/ShowPreferencesAction.java @@ -15,7 +15,10 @@ 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; @@ -23,7 +26,8 @@ public ShowPreferencesAction(LibraryTabContainer tabContainer, Class implements PreferencesTab { - private static final String HUGGING_FACE_CHAT_MODEL_PROMPT = "TinyLlama/TinyLlama_v1.1 (or any other model name)"; + private static final String HUGGING_FACE_CHAT_MODEL_PROMPT = + "TinyLlama/TinyLlama_v1.1 (or any other model name)"; @FXML private CheckBox enableAi; @@ -52,9 +54,7 @@ public class AiTab extends AbstractPreferenceTabView implements private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer(); public AiTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } public void initialize() { @@ -66,116 +66,213 @@ public void initialize() { .withText(AiProvider::toString) .install(aiProviderComboBox); aiProviderComboBox.setItems(viewModel.aiProvidersProperty()); - aiProviderComboBox.valueProperty().bindBidirectional(viewModel.selectedAiProviderProperty()); + aiProviderComboBox + .valueProperty() + .bindBidirectional(viewModel.selectedAiProviderProperty()); aiProviderComboBox.disableProperty().bind(viewModel.disableBasicSettingsProperty()); - new ViewModelListCellFactory() - .withText(text -> text) - .install(chatModelComboBox); + new ViewModelListCellFactory().withText(text -> text).install(chatModelComboBox); chatModelComboBox.setItems(viewModel.chatModelsProperty()); chatModelComboBox.valueProperty().bindBidirectional(viewModel.selectedChatModelProperty()); chatModelComboBox.disableProperty().bind(viewModel.disableBasicSettingsProperty()); - this.aiProviderComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { - if (newValue == AiProvider.HUGGING_FACE) { - chatModelComboBox.setPromptText(HUGGING_FACE_CHAT_MODEL_PROMPT); - } - }); + this.aiProviderComboBox + .valueProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (newValue == AiProvider.HUGGING_FACE) { + chatModelComboBox.setPromptText(HUGGING_FACE_CHAT_MODEL_PROMPT); + } + }); apiKeyTextField.textProperty().bindBidirectional(viewModel.apiKeyProperty()); apiKeyTextField.disableProperty().bind(viewModel.disableBasicSettingsProperty()); - customizeExpertSettingsCheckbox.selectedProperty().bindBidirectional(viewModel.customizeExpertSettingsProperty()); - customizeExpertSettingsCheckbox.disableProperty().bind(viewModel.disableBasicSettingsProperty()); + customizeExpertSettingsCheckbox + .selectedProperty() + .bindBidirectional(viewModel.customizeExpertSettingsProperty()); + customizeExpertSettingsCheckbox + .disableProperty() + .bind(viewModel.disableBasicSettingsProperty()); new ViewModelListCellFactory() .withText(EmbeddingModel::fullInfo) .install(embeddingModelComboBox); embeddingModelComboBox.setItems(viewModel.embeddingModelsProperty()); - embeddingModelComboBox.valueProperty().bindBidirectional(viewModel.selectedEmbeddingModelProperty()); + 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.disableApiBaseUrlProperty().addListener((observable, oldValue, newValue) -> - apiBaseUrlTextField.setDisable(newValue || viewModel.disableExpertSettingsProperty().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())); instructionTextArea.textProperty().bindBidirectional(viewModel.instructionProperty()); instructionTextArea.disableProperty().bind(viewModel.disableExpertSettingsProperty()); - // 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); - }); - - viewModel.contextWindowSizeProperty().addListener((observable, oldValue, newValue) -> { - contextWindowSizeTextField.valueProperty().set(newValue == null ? 0 : newValue.intValue()); - }); - - contextWindowSizeTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); + 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()); + }); + + 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); - }); - - 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); - }); - - 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); - }); - - viewModel.ragMaxResultsCountProperty().addListener((observable, oldValue, newValue) -> { - ragMaxResultsCountTextField.valueProperty().set(newValue == null ? 0 : newValue.intValue()); - }); - - ragMaxResultsCountTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); + 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()); + }); + + documentSplitterChunkSizeTextField + .disableProperty() + .bind(viewModel.disableExpertSettingsProperty()); + + 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()); + }); + + documentSplitterOverlapSizeTextField + .disableProperty() + .bind(viewModel.disableExpertSettingsProperty()); + + 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()); + }); + + ragMaxResultsCountTextField + .disableProperty() + .bind(viewModel.disableExpertSettingsProperty()); ragMinScoreTextField.textProperty().bindBidirectional(viewModel.ragMinScoreProperty()); ragMinScoreTextField.disableProperty().bind(viewModel.disableExpertSettingsProperty()); - Platform.runLater(() -> { - visualizer.initVisualization(viewModel.getApiTokenValidationStatus(), apiKeyTextField); - visualizer.initVisualization(viewModel.getChatModelValidationStatus(), chatModelComboBox); - visualizer.initVisualization(viewModel.getApiBaseUrlValidationStatus(), apiBaseUrlTextField); - visualizer.initVisualization(viewModel.getEmbeddingModelValidationStatus(), embeddingModelComboBox); - visualizer.initVisualization(viewModel.getSystemMessageValidationStatus(), instructionTextArea); - 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.getRagMinScoreTypeValidationStatus(), ragMinScoreTextField); - visualizer.initVisualization(viewModel.getRagMinScoreRangeValidationStatus(), ragMinScoreTextField); - }); + Platform.runLater( + () -> { + visualizer.initVisualization( + viewModel.getApiTokenValidationStatus(), apiKeyTextField); + visualizer.initVisualization( + viewModel.getChatModelValidationStatus(), chatModelComboBox); + visualizer.initVisualization( + viewModel.getApiBaseUrlValidationStatus(), apiBaseUrlTextField); + visualizer.initVisualization( + viewModel.getEmbeddingModelValidationStatus(), embeddingModelComboBox); + visualizer.initVisualization( + viewModel.getSystemMessageValidationStatus(), instructionTextArea); + 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.getRagMinScoreTypeValidationStatus(), ragMinScoreTextField); + visualizer.initVisualization( + viewModel.getRagMinScoreRangeValidationStatus(), ragMinScoreTextField); + }); 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_GENERAL_SETTINGS, + dialogService, + preferences.getExternalApplicationsPreferences()), + generalSettingsHelp); + actionFactory.configureIconButton( + StandardActions.HELP, + new HelpAction( + HelpFile.AI_EXPERT_SETTINGS, + dialogService, + preferences.getExternalApplicationsPreferences()), + expertSettingsHelp); } @Override diff --git a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java index 8ba7c3a66c0d..88107f7e7b97 100644 --- a/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/ai/AiTabViewModel.java @@ -1,9 +1,9 @@ package org.jabref.gui.preferences.ai; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; @@ -28,10 +28,10 @@ import org.jabref.model.ai.EmbeddingModel; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; public class AiTabViewModel implements PreferenceTabViewModel { private final Locale oldLocale; @@ -63,10 +63,14 @@ public class AiTabViewModel implements PreferenceTabViewModel { private final ListProperty embeddingModelsList = new SimpleListProperty<>(FXCollections.observableArrayList(EmbeddingModel.values())); - private final ObjectProperty selectedEmbeddingModel = new SimpleObjectProperty<>(); + 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(); @@ -105,176 +109,225 @@ public AiTabViewModel(CliPreferences preferences) { this.aiPreferences = preferences.getAiPreferences(); - this.enableAi.addListener((observable, oldValue, newValue) -> { - disableBasicSettings.set(!newValue); - disableExpertSettings.set(!newValue || !customizeExpertSettings.get()); - }); - - this.customizeExpertSettings.addListener((observableValue, oldValue, newValue) -> - disableExpertSettings.set(!newValue || !enableAi.get()) - ); - - this.selectedAiProvider.addListener((observable, oldValue, newValue) -> { - List models = AiDefaultPreferences.AVAILABLE_CHAT_MODELS.get(newValue); - - // When we setAll on Hugging Face, models are empty, and currentChatModel become null. - // It becomes null beause currentChatModel is binded 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); - - disableApiBaseUrl.set(newValue == AiProvider.HUGGING_FACE || newValue == AiProvider.GEMINI); - - if (oldValue != null) { - switch (oldValue) { - case OPEN_AI -> { - openAiChatModel.set(oldChatModel); - openAiApiKey.set(currentApiKey.get()); - openAiApiBaseUrl.set(currentApiBaseUrl.get()); + this.enableAi.addListener( + (observable, oldValue, newValue) -> { + disableBasicSettings.set(!newValue); + disableExpertSettings.set(!newValue || !customizeExpertSettings.get()); + }); + + this.customizeExpertSettings.addListener( + (observableValue, oldValue, newValue) -> + disableExpertSettings.set(!newValue || !enableAi.get())); + + this.selectedAiProvider.addListener( + (observable, oldValue, newValue) -> { + List models = AiDefaultPreferences.AVAILABLE_CHAT_MODELS.get(newValue); + + // When we setAll on Hugging Face, models are empty, and currentChatModel become + // null. + // It becomes null beause currentChatModel is binded 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); + + disableApiBaseUrl.set( + newValue == AiProvider.HUGGING_FACE || newValue == AiProvider.GEMINI); + + if (oldValue != null) { + switch (oldValue) { + case OPEN_AI -> { + openAiChatModel.set(oldChatModel); + openAiApiKey.set(currentApiKey.get()); + openAiApiBaseUrl.set(currentApiBaseUrl.get()); + } + case MISTRAL_AI -> { + mistralAiChatModel.set(oldChatModel); + mistralAiApiKey.set(currentApiKey.get()); + mistralAiApiBaseUrl.set(currentApiBaseUrl.get()); + } + case GEMINI -> { + geminiChatModel.set(oldChatModel); + geminiAiApiKey.set(currentApiKey.get()); + geminiApiBaseUrl.set(currentApiBaseUrl.get()); + } + case HUGGING_FACE -> { + huggingFaceChatModel.set(oldChatModel); + huggingFaceApiKey.set(currentApiKey.get()); + huggingFaceApiBaseUrl.set(currentApiBaseUrl.get()); + } + } } - case MISTRAL_AI -> { - mistralAiChatModel.set(oldChatModel); - mistralAiApiKey.set(currentApiKey.get()); - mistralAiApiBaseUrl.set(currentApiBaseUrl.get()); - } - case GEMINI -> { - geminiChatModel.set(oldChatModel); - geminiAiApiKey.set(currentApiKey.get()); - geminiApiBaseUrl.set(currentApiBaseUrl.get()); + + switch (newValue) { + case OPEN_AI -> { + currentChatModel.set(openAiChatModel.get()); + currentApiKey.set(openAiApiKey.get()); + currentApiBaseUrl.set(openAiApiBaseUrl.get()); + } + case MISTRAL_AI -> { + currentChatModel.set(mistralAiChatModel.get()); + currentApiKey.set(mistralAiApiKey.get()); + currentApiBaseUrl.set(mistralAiApiBaseUrl.get()); + } + case GEMINI -> { + currentChatModel.set(geminiChatModel.get()); + currentApiKey.set(geminiAiApiKey.get()); + currentApiBaseUrl.set(geminiApiBaseUrl.get()); + } + case HUGGING_FACE -> { + currentChatModel.set(huggingFaceChatModel.get()); + currentApiKey.set(huggingFaceApiKey.get()); + currentApiBaseUrl.set(huggingFaceApiBaseUrl.get()); + } } - case HUGGING_FACE -> { - huggingFaceChatModel.set(oldChatModel); - huggingFaceApiKey.set(currentApiKey.get()); - huggingFaceApiBaseUrl.set(currentApiBaseUrl.get()); + }); + + this.currentChatModel.addListener( + (observable, oldValue, newValue) -> { + switch (selectedAiProvider.get()) { + case OPEN_AI -> openAiChatModel.set(newValue); + case MISTRAL_AI -> mistralAiChatModel.set(newValue); + case GEMINI -> geminiChatModel.set(newValue); + case HUGGING_FACE -> huggingFaceChatModel.set(newValue); } - } - } - - switch (newValue) { - case OPEN_AI -> { - currentChatModel.set(openAiChatModel.get()); - currentApiKey.set(openAiApiKey.get()); - currentApiBaseUrl.set(openAiApiBaseUrl.get()); - } - case MISTRAL_AI -> { - currentChatModel.set(mistralAiChatModel.get()); - currentApiKey.set(mistralAiApiKey.get()); - currentApiBaseUrl.set(mistralAiApiBaseUrl.get()); - } - case GEMINI -> { - currentChatModel.set(geminiChatModel.get()); - currentApiKey.set(geminiAiApiKey.get()); - currentApiBaseUrl.set(geminiApiBaseUrl.get()); - } - case HUGGING_FACE -> { - currentChatModel.set(huggingFaceChatModel.get()); - currentApiKey.set(huggingFaceApiKey.get()); - currentApiBaseUrl.set(huggingFaceApiBaseUrl.get()); - } - } - }); - - this.currentChatModel.addListener((observable, oldValue, newValue) -> { - switch (selectedAiProvider.get()) { - case OPEN_AI -> openAiChatModel.set(newValue); - case MISTRAL_AI -> mistralAiChatModel.set(newValue); - case GEMINI -> geminiChatModel.set(newValue); - case HUGGING_FACE -> huggingFaceChatModel.set(newValue); - } - Map modelContextWindows = AiDefaultPreferences.CONTEXT_WINDOW_SIZES.get(selectedAiProvider.get()); + Map modelContextWindows = + AiDefaultPreferences.CONTEXT_WINDOW_SIZES.get(selectedAiProvider.get()); - if (modelContextWindows == null) { - contextWindowSize.set(AiDefaultPreferences.CONTEXT_WINDOW_SIZE); - return; - } - - contextWindowSize.set(modelContextWindows.getOrDefault(newValue, AiDefaultPreferences.CONTEXT_WINDOW_SIZE)); - }); + if (modelContextWindows == null) { + contextWindowSize.set(AiDefaultPreferences.CONTEXT_WINDOW_SIZE); + return; + } - this.currentApiKey.addListener((observable, oldValue, newValue) -> { - switch (selectedAiProvider.get()) { - case OPEN_AI -> openAiApiKey.set(newValue); - case MISTRAL_AI -> mistralAiApiKey.set(newValue); - case GEMINI -> geminiAiApiKey.set(newValue); - case HUGGING_FACE -> huggingFaceApiKey.set(newValue); - } - }); - - this.currentApiBaseUrl.addListener((observable, oldValue, newValue) -> { - switch (selectedAiProvider.get()) { - case OPEN_AI -> openAiApiBaseUrl.set(newValue); - case MISTRAL_AI -> mistralAiApiBaseUrl.set(newValue); - case GEMINI -> geminiApiBaseUrl.set(newValue); - case HUGGING_FACE -> huggingFaceApiBaseUrl.set(newValue); - } - }); - - 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, - chatModel -> !StringUtil.isBlank(chatModel), - ValidationMessage.error(Localization.lang("Chat model has to be provided"))); - - 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, - ValidationMessage.error(Localization.lang("Embedding model has to be provided"))); - - this.instructionValidator = new FunctionBasedValidator<>( - instruction, - message -> !StringUtil.isBlank(message), - ValidationMessage.error(Localization.lang("The instruction has to be provided"))); - - 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, - 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, - ValidationMessage.error(Localization.lang("Context window size must be greater than 0"))); - - 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, - 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"))); - - 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, - minScore -> LocalizedNumbers.stringToDouble(minScore).isPresent(), - ValidationMessage.error(Localization.lang("RAG minimum score must be a number"))); - - 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"))); + contextWindowSize.set( + modelContextWindows.getOrDefault( + newValue, AiDefaultPreferences.CONTEXT_WINDOW_SIZE)); + }); + + this.currentApiKey.addListener( + (observable, oldValue, newValue) -> { + switch (selectedAiProvider.get()) { + case OPEN_AI -> openAiApiKey.set(newValue); + case MISTRAL_AI -> mistralAiApiKey.set(newValue); + case GEMINI -> geminiAiApiKey.set(newValue); + case HUGGING_FACE -> huggingFaceApiKey.set(newValue); + } + }); + + this.currentApiBaseUrl.addListener( + (observable, oldValue, newValue) -> { + switch (selectedAiProvider.get()) { + case OPEN_AI -> openAiApiBaseUrl.set(newValue); + case MISTRAL_AI -> mistralAiApiBaseUrl.set(newValue); + case GEMINI -> geminiApiBaseUrl.set(newValue); + case HUGGING_FACE -> huggingFaceApiBaseUrl.set(newValue); + } + }); + + 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, + chatModel -> !StringUtil.isBlank(chatModel), + ValidationMessage.error( + Localization.lang("Chat model has to be provided"))); + + 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, + ValidationMessage.error( + Localization.lang("Embedding model has to be provided"))); + + this.instructionValidator = + new FunctionBasedValidator<>( + instruction, + message -> !StringUtil.isBlank(message), + ValidationMessage.error( + Localization.lang("The instruction has to be provided"))); + + 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, + 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, + ValidationMessage.error( + Localization.lang("Context window size must be greater than 0"))); + + 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, + 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"))); + + 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, + minScore -> LocalizedNumbers.stringToDouble(minScore).isPresent(), + ValidationMessage.error( + Localization.lang("RAG minimum score must be a number"))); + + 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"))); } @Override @@ -316,15 +369,24 @@ public void storeSettings() { aiPreferences.setAiProvider(selectedAiProvider.get()); - aiPreferences.setOpenAiChatModel(openAiChatModel.get() == null ? "" : openAiChatModel.get()); - aiPreferences.setMistralAiChatModel(mistralAiChatModel.get() == null ? "" : mistralAiChatModel.get()); - aiPreferences.setGeminiChatModel(geminiChatModel.get() == null ? "" : geminiChatModel.get()); - aiPreferences.setHuggingFaceChatModel(huggingFaceChatModel.get() == null ? "" : huggingFaceChatModel.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.setOpenAiChatModel( + openAiChatModel.get() == null ? "" : openAiChatModel.get()); + aiPreferences.setMistralAiChatModel( + mistralAiChatModel.get() == null ? "" : mistralAiChatModel.get()); + aiPreferences.setGeminiChatModel( + geminiChatModel.get() == null ? "" : geminiChatModel.get()); + aiPreferences.setHuggingFaceChatModel( + huggingFaceChatModel.get() == null ? "" : huggingFaceChatModel.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()); // We notify in all cases without a real check if something was changed aiPreferences.apiKeyUpdated(); @@ -332,28 +394,39 @@ public void storeSettings() { aiPreferences.setEmbeddingModel(selectedEmbeddingModel.get()); - aiPreferences.setOpenAiApiBaseUrl(openAiApiBaseUrl.get() == null ? "" : openAiApiBaseUrl.get()); - aiPreferences.setMistralAiApiBaseUrl(mistralAiApiBaseUrl.get() == null ? "" : mistralAiApiBaseUrl.get()); - aiPreferences.setGeminiApiBaseUrl(geminiApiBaseUrl.get() == null ? "" : geminiApiBaseUrl.get()); - aiPreferences.setHuggingFaceApiBaseUrl(huggingFaceApiBaseUrl.get() == null ? "" : huggingFaceApiBaseUrl.get()); + aiPreferences.setOpenAiApiBaseUrl( + openAiApiBaseUrl.get() == null ? "" : openAiApiBaseUrl.get()); + aiPreferences.setMistralAiApiBaseUrl( + mistralAiApiBaseUrl.get() == null ? "" : mistralAiApiBaseUrl.get()); + aiPreferences.setGeminiApiBaseUrl( + geminiApiBaseUrl.get() == null ? "" : geminiApiBaseUrl.get()); + aiPreferences.setHuggingFaceApiBaseUrl( + huggingFaceApiBaseUrl.get() == null ? "" : huggingFaceApiBaseUrl.get()); aiPreferences.setInstruction(instruction.get()); - // 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()); + // 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()); aiPreferences.setDocumentSplitterOverlapSize(documentSplitterOverlapSize.get()); aiPreferences.setRagMaxResultsCount(ragMaxResultsCount.get()); - aiPreferences.setRagMinScore(LocalizedNumbers.stringToDouble(oldLocale, ragMinScore.get()).get()); + aiPreferences.setRagMinScore( + LocalizedNumbers.stringToDouble(oldLocale, ragMinScore.get()).get()); } public void resetExpertSettings() { - String resetApiBaseUrl = AiDefaultPreferences.PROVIDERS_API_URLS.get(selectedAiProvider.get()); + String resetApiBaseUrl = + AiDefaultPreferences.PROVIDERS_API_URLS.get(selectedAiProvider.get()); currentApiBaseUrl.set(resetApiBaseUrl); instruction.set(AiDefaultPreferences.SYSTEM_MESSAGE); - int resetContextWindowSize = AiDefaultPreferences.CONTEXT_WINDOW_SIZES.getOrDefault(selectedAiProvider.get(), Map.of()).getOrDefault(currentChatModel.get(), 0); + int resetContextWindowSize = + AiDefaultPreferences.CONTEXT_WINDOW_SIZES + .getOrDefault(selectedAiProvider.get(), Map.of()) + .getOrDefault(currentChatModel.get(), 0); contextWindowSize.set(resetContextWindowSize); temperature.set(LocalizedNumbers.doubleToString(AiDefaultPreferences.TEMPERATURE)); @@ -377,30 +450,36 @@ 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); + return validators.stream() + .map(Validator::getValidationStatus) + .allMatch(ValidationStatus::isValid); } public boolean validateExpertSettings() { - List validators = List.of( - apiBaseUrlValidator, - embeddingModelValidator, - instructionValidator, - temperatureTypeValidator, - temperatureRangeValidator, - contextWindowSizeValidator, - documentSplitterChunkSizeValidator, - documentSplitterOverlapSizeValidator, - ragMaxResultsCountValidator, - ragMinScoreTypeValidator, - ragMinScoreRangeValidator - ); - - return validators.stream().map(Validator::getValidationStatus).allMatch(ValidationStatus::isValid); + List validators = + List.of( + apiBaseUrlValidator, + embeddingModelValidator, + instructionValidator, + temperatureTypeValidator, + temperatureRangeValidator, + contextWindowSizeValidator, + documentSplitterChunkSizeValidator, + documentSplitterOverlapSizeValidator, + ragMaxResultsCountValidator, + ragMinScoreTypeValidator, + ragMinScoreRangeValidator); + + return validators.stream() + .map(Validator::getValidationStatus) + .allMatch(ValidationStatus::isValid); } public BooleanProperty enableAi() { diff --git a/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java b/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java index d8837eb0443c..91549fd32fc6 100644 --- a/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java +++ b/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTab.java @@ -1,5 +1,8 @@ package org.jabref.gui.preferences.autocompletion; +import com.airhacks.afterburner.views.ViewLoader; +import com.dlsc.gemsfx.TagsField; + import javafx.css.PseudoClass; import javafx.fxml.FXML; import javafx.scene.Node; @@ -15,10 +18,8 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import com.dlsc.gemsfx.TagsField; - -public class AutoCompletionTab extends AbstractPreferenceTabView implements PreferencesTab { +public class AutoCompletionTab extends AbstractPreferenceTabView + implements PreferencesTab { private static final PseudoClass FOCUSED = PseudoClass.getPseudoClass("focused"); @FXML private CheckBox enableAutoComplete; @@ -31,9 +32,7 @@ public class AutoCompletionTab extends AbstractPreferenceTabView().withText(Field::getDisplayName)); - autoCompleteFields.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); + autoCompleteFields.setCellFactory( + new ViewModelListCellFactory().withText(Field::getDisplayName)); + autoCompleteFields.setSuggestionProvider( + request -> viewModel.getSuggestions(request.getUserText())); autoCompleteFields.tagsProperty().bindBidirectional(viewModel.autoCompleteFieldsProperty()); autoCompleteFields.setConverter(viewModel.getFieldStringConverter()); autoCompleteFields.setTagViewFactory(this::createTag); autoCompleteFields.setShowSearchIcon(false); - autoCompleteFields.setOnMouseClicked(event -> autoCompleteFields.getEditor().requestFocus()); + 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) { diff --git a/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java b/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java index 125b1a7b8300..58a424984b0a 100644 --- a/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/autocompletion/AutoCompletionTabViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.preferences.autocompletion; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -18,10 +14,15 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + 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(); @@ -40,10 +41,13 @@ public AutoCompletionTabViewModel(AutoCompletePreferences autoCompletePreference @Override public void setValues() { enableAutoCompleteProperty.setValue(autoCompletePreferences.shouldAutoComplete()); - autoCompleteFieldsProperty.setValue(FXCollections.observableArrayList(autoCompletePreferences.getCompleteFields())); - if (autoCompletePreferences.getNameFormat() == AutoCompletePreferences.NameFormat.FIRST_LAST) { + 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 { autoCompleteBothProperty.setValue(true); @@ -141,7 +145,11 @@ 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()); + .filter( + field -> + field.getDisplayName() + .toLowerCase() + .contains(request.toLowerCase())) + .collect(Collectors.toList()); } } diff --git a/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java b/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java index 99133e31a1ce..a4f4cc5711d3 100644 --- a/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java +++ b/src/main/java/org/jabref/gui/preferences/citationkeypattern/CitationKeyPatternTab.java @@ -1,5 +1,8 @@ package org.jabref.gui.preferences.citationkeypattern; +import com.airhacks.afterburner.injection.Injector; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; @@ -16,10 +19,8 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntryTypesManager; -import com.airhacks.afterburner.injection.Injector; -import com.airhacks.afterburner.views.ViewLoader; - -public class CitationKeyPatternTab extends AbstractPreferenceTabView implements PreferencesTab { +public class CitationKeyPatternTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private CheckBox overwriteAllow; @FXML private CheckBox overwriteWarning; @@ -34,9 +35,7 @@ public class CitationKeyPatternTab extends AbstractPreferenceTabView patternListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ObjectProperty defaultKeyPatternProperty = new SimpleObjectProperty<>( - new CitationKeyPatternsPanelItemModel(new CitationKeyPatternsPanelViewModel.DefaultEntryType(), "")); + // 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; @@ -70,23 +75,27 @@ public void setValues() { @Override public void storeSettings() { GlobalCitationKeyPatterns newKeyPattern = - new GlobalCitationKeyPatterns(keyPatternPreferences.getKeyPatterns().getDefaultValue()); - patternListProperty.forEach(item -> { - String patternString = item.getPattern(); - if (!"default".equals(item.getEntryType().getName())) { - if (!patternString.trim().isEmpty()) { - newKeyPattern.addCitationKeyPattern(item.getEntryType(), patternString); - } - } - }); + new GlobalCitationKeyPatterns( + keyPatternPreferences.getKeyPatterns().getDefaultValue()); + patternListProperty.forEach( + item -> { + String patternString = item.getPattern(); + if (!"default".equals(item.getEntryType().getName())) { + if (!patternString.trim().isEmpty()) { + newKeyPattern.addCitationKeyPattern(item.getEntryType(), patternString); + } + } + }); 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()); } - CitationKeyPatternPreferences.KeySuffix keySuffix = CitationKeyPatternPreferences.KeySuffix.ALWAYS; + CitationKeyPatternPreferences.KeySuffix keySuffix = + CitationKeyPatternPreferences.KeySuffix.ALWAYS; if (letterStartAProperty.getValue()) { keySuffix = CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A; diff --git a/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java b/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java index 45dbb9ad7963..38edd02cbe93 100644 --- a/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypeViewModel.java @@ -1,10 +1,10 @@ package org.jabref.gui.preferences.customentrytypes; -import java.util.function.Predicate; - import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.field.Field; +import java.util.function.Predicate; + /** * This class is required to check whether a delete button should be displayed at {@link org.jabref.gui.preferences.customentrytypes.CustomEntryTypesTab#setupEntryTypesTable()} */ diff --git a/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java b/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java index 8bdd84d2b02c..e867053a7d21 100644 --- a/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java +++ b/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTab.java @@ -1,5 +1,12 @@ package org.jabref.gui.preferences.customentrytypes; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; + import javafx.application.Platform; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.ObservableList; @@ -36,12 +43,8 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; - -public class CustomEntryTypesTab extends AbstractPreferenceTabView implements PreferencesTab { +public class CustomEntryTypesTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TableView entryTypesTable; @FXML private TableColumn entryTypColumn; @@ -63,9 +66,7 @@ public class CustomEntryTypesTab extends AbstractPreferenceTabView { - visualizer.initVisualization(viewModel.entryTypeValidationStatus(), addNewEntryType, true); - visualizer.initVisualization(viewModel.fieldValidationStatus(), addNewField, true); - }); + addNewEntryTypeButton + .disableProperty() + .bind(viewModel.entryTypeValidationStatus().validProperty().not()); + addNewFieldButton + .disableProperty() + .bind( + viewModel + .fieldValidationStatus() + .validProperty() + .not() + .or(viewModel.selectedEntryTypeProperty().isNull())); + + Platform.runLater( + () -> { + visualizer.initVisualization( + viewModel.entryTypeValidationStatus(), addNewEntryType, true); + visualizer.initVisualization( + viewModel.fieldValidationStatus(), addNewField, true); + }); } 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())); + entryTypeActionsColumn.setCellValueFactory( + cellData -> + new ReadOnlyStringWrapper( + cellData.getValue().entryType().get().getType().getDisplayName())); new ValueTableCellFactory() - .withGraphic((type, name) -> { - 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, name) -> { - if (type instanceof CustomEntryTypeViewModel) { - return evt -> viewModel.removeEntryType(entryTypesTable.getSelectionModel().getSelectedItem()); - } else { - return evt -> { - }; - } - }) + .withGraphic( + (type, name) -> { + 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, name) -> { + if (type instanceof CustomEntryTypeViewModel) { + return evt -> + viewModel.removeEntryType( + entryTypesTable + .getSelectionModel() + .getSelectedItem()); + } else { + return evt -> {}; + } + }) .install(entryTypeActionsColumn); - viewModel.selectedEntryTypeProperty().bind(entryTypesTable.getSelectionModel().selectedItemProperty()); + viewModel + .selectedEntryTypeProperty() + .bind(entryTypesTable.getSelectionModel().selectedItemProperty()); viewModel.entryTypeToAddProperty().bindBidirectional(addNewEntryType.textProperty()); - EasyBind.subscribe(viewModel.selectedEntryTypeProperty(), type -> { - if (type != null) { - var items = type.fields(); - fields.setItems(items); - } else { - fields.setItems(null); - } - }); + EasyBind.subscribe( + viewModel.selectedEntryTypeProperty(), + type -> { + if (type != null) { + var items = type.fields(); + fields.setItems(items); + } else { + fields.setItems(null); + } + }); } private void setupFieldsTable() { fieldNameColumn.setCellValueFactory(item -> item.getValue().displayNameProperty()); fieldNameColumn.setCellFactory(TextFieldTableCell.forTableColumn()); fieldNameColumn.setEditable(true); - fieldNameColumn.setOnEditCommit((TableColumn.CellEditEvent event) -> { - String newDisplayName = event.getNewValue(); - if (newDisplayName.isBlank()) { - dialogService.notify(Localization.lang("Name cannot be empty")); - event.getTableView().edit(-1, null); - event.getTableView().refresh(); - return; - } + fieldNameColumn.setOnEditCommit( + (TableColumn.CellEditEvent event) -> { + String newDisplayName = event.getNewValue(); + if (newDisplayName.isBlank()) { + dialogService.notify(Localization.lang("Name cannot be empty")); + event.getTableView().edit(-1, null); + event.getTableView().refresh(); + return; + } - FieldViewModel fieldViewModel = event.getRowValue(); - 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); - if (fieldExists) { - dialogService.notify(Localization.lang("Unable to change field name. \"%0\" already in use.", newDisplayName)); - event.getTableView().edit(-1, null); - } else { - fieldViewModel.displayNameProperty().setValue(newDisplayName); - } - event.getTableView().refresh(); - }); + FieldViewModel fieldViewModel = event.getRowValue(); + 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); + if (fieldExists) { + dialogService.notify( + Localization.lang( + "Unable to change field name. \"%0\" already in use.", + newDisplayName)); + event.getTableView().edit(-1, null); + } else { + fieldViewModel.displayNameProperty().setValue(newDisplayName); + } + event.getTableView().refresh(); + }); fieldTypeColumn.setCellFactory(CheckBoxTableCell.forTableColumn(fieldTypeColumn)); fieldTypeColumn.setCellValueFactory(item -> item.getValue().requiredProperty()); makeRotatedColumnHeader(fieldTypeColumn, Localization.lang("Required")); - fieldTypeMultilineColumn.setCellFactory(CheckBoxTableCell.forTableColumn(fieldTypeMultilineColumn)); + fieldTypeMultilineColumn.setCellFactory( + CheckBoxTableCell.forTableColumn(fieldTypeMultilineColumn)); fieldTypeMultilineColumn.setCellValueFactory(item -> item.getValue().multilineProperty()); makeRotatedColumnHeader(fieldTypeMultilineColumn, Localization.lang("Multiline")); fieldTypeActionColumn.setSortable(false); fieldTypeActionColumn.setReorderable(false); fieldTypeActionColumn.setEditable(false); - fieldTypeActionColumn.setCellValueFactory(cellData -> cellData.getValue().displayNameProperty()); + fieldTypeActionColumn.setCellValueFactory( + cellData -> cellData.getValue().displayNameProperty()); new ValueTableCellFactory() .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) - .withTooltip(name -> Localization.lang("Remove field %0 from currently selected entry type", name)) - .withOnMouseClickedEvent(item -> evt -> viewModel.removeField(fields.getSelectionModel().getSelectedItem())) + .withTooltip( + name -> + Localization.lang( + "Remove field %0 from currently selected entry type", name)) + .withOnMouseClickedEvent( + item -> + evt -> + viewModel.removeField( + fields.getSelectionModel().getSelectedItem())) .install(fieldTypeActionColumn); new ViewModelTableRowFactory() @@ -201,9 +253,12 @@ private void setupFieldsTable() { 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 +270,17 @@ 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(); @@ -236,7 +292,8 @@ private void handleOnDragDetected(TableRow row, FieldViewModel f event.consume(); } - private void handleOnDragDropped(TableRow row, FieldViewModel originalItem, DragEvent event) { + private void handleOnDragDropped( + TableRow row, FieldViewModel originalItem, DragEvent event) { if (localDragboard.hasType(FieldViewModel.class)) { FieldViewModel field = localDragboard.getValue(FieldViewModel.class); fields.getItems().remove(field); @@ -253,7 +310,8 @@ private void handleOnDragDropped(TableRow row, FieldViewModel or event.consume(); } - private void handleOnDragExited(TableRow row, FieldViewModel fieldViewModel, DragEvent dragEvent) { + private void handleOnDragExited( + TableRow row, FieldViewModel fieldViewModel, DragEvent dragEvent) { ControlHelper.removeDroppingPseudoClasses(row); } @@ -271,10 +329,12 @@ void addNewField() { @FXML 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("Reset to default")); + 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("Reset to default")); if (reset) { viewModel.resetAllCustomEntryTypes(); fields.getSelectionModel().clearSelection(); diff --git a/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java b/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java index ea773ee88e97..e240fae58aad 100644 --- a/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/customentrytypes/CustomEntryTypesTabViewModel.java @@ -1,13 +1,9 @@ package org.jabref.gui.preferences.customentrytypes; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.Observable; import javafx.beans.property.ObjectProperty; @@ -34,18 +30,26 @@ import org.jabref.model.entry.types.UnknownEntryType; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; public class CustomEntryTypesTabViewModel implements PreferenceTabViewModel { - private final ObservableList fieldsForAdding = FXCollections.observableArrayList(FieldFactory.getStandardFieldsWithCitationKey()); - private final ObjectProperty selectedEntryType = new SimpleObjectProperty<>(); + 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; @@ -57,12 +61,16 @@ public class CustomEntryTypesTabViewModel implements PreferenceTabViewModel { 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; @@ -70,14 +78,19 @@ public CustomEntryTypesTabViewModel(BibDatabaseMode mode, this.multiLineFields.addAll(preferences.getFieldPreferences().getNonWrappableFields()); - entryTypeValidator = new FunctionBasedValidator<>( - entryTypeToAdd, - StringUtil::isNotBlank, - ValidationMessage.error(Localization.lang("Entry type cannot be empty. Please enter a name."))); - fieldValidator = new FunctionBasedValidator<>( - newFieldToAdd, - input -> (input != null) && StringUtil.isNotBlank(input.getDisplayName()), - ValidationMessage.error(Localization.lang("Field cannot be empty. Please enter a name."))); + entryTypeValidator = + new FunctionBasedValidator<>( + entryTypeToAdd, + StringUtil::isNotBlank, + ValidationMessage.error( + Localization.lang( + "Entry type cannot be empty. Please enter a name."))); + fieldValidator = + new FunctionBasedValidator<>( + newFieldToAdd, + input -> (input != null) && StringUtil.isNotBlank(input.getDisplayName()), + ValidationMessage.error( + Localization.lang("Field cannot be empty. Please enter a name."))); } @Override @@ -108,18 +121,23 @@ public void storeSettings() { EntryType newPlainType = type.getType(); // Collect multilineFields for storage in preferences later - multilineFields.addAll(allFields.stream() - .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()); - - List fields = allFields.stream().map(model -> model.toBibField(newPlainType)).collect(Collectors.toList()); + multilineFields.addAll( + allFields.stream() + .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()); + + List fields = + allFields.stream() + .map(model -> model.toBibField(newPlainType)) + .collect(Collectors.toList()); BibEntryType newType = new BibEntryType(newPlainType, fields, required); @@ -136,7 +154,8 @@ public void storeSettings() { public EntryTypeViewModel addNewCustomEntryType() { EntryType newentryType = new UnknownEntryType(entryTypeToAdd.getValue()); - BibEntryType type = new BibEntryType(newentryType, new ArrayList<>(), Collections.emptyList()); + BibEntryType type = + new BibEntryType(newentryType, new ArrayList<>(), Collections.emptyList()); EntryTypeViewModel viewModel = new CustomEntryTypeViewModel(type, isMultiline); this.entryTypesWithFields.add(viewModel); this.entryTypeToAdd.setValue(""); @@ -156,21 +175,31 @@ public void addNewField() { if (fieldExists) { dialogService.showWarningDialogAndWait( Localization.lang("Duplicate fields"), - Localization.lang("Warning: You added field \"%0\" twice. Only one will be kept.", field.getDisplayName())); + 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)); + 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) { diff --git a/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java index 11c99161151a..5a5db08aab97 100644 --- a/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/customentrytypes/EntryTypeViewModel.java @@ -1,9 +1,6 @@ package org.jabref.gui.preferences.customentrytypes; -import java.util.List; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.stream.Collectors; +import static org.jabref.gui.preferences.customentrytypes.FieldViewModel.Mandatory; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -13,7 +10,10 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.field.Field; -import static org.jabref.gui.preferences.customentrytypes.FieldViewModel.Mandatory; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; public class EntryTypeViewModel { @@ -22,12 +22,18 @@ public class EntryTypeViewModel { 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()); + 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()); fields = FXCollections.observableArrayList(allFieldsForType); } diff --git a/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java b/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java index ae987a276524..c9220674d05d 100644 --- a/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/customentrytypes/FieldViewModel.java @@ -22,10 +22,8 @@ public class FieldViewModel { 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); diff --git a/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java b/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java index f5b409f6002d..73b4b244c63a 100644 --- a/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java +++ b/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTab.java @@ -1,5 +1,8 @@ package org.jabref.gui.preferences.customexporter; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + import javafx.fxml.FXML; import javafx.scene.control.SelectionMode; import javafx.scene.control.TableColumn; @@ -10,10 +13,8 @@ import org.jabref.gui.preferences.PreferencesTab; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; - -public class CustomExporterTab extends AbstractPreferenceTabView implements PreferencesTab { +public class CustomExporterTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TableView exporterTable; @FXML private TableColumn nameColumn; @@ -21,9 +22,7 @@ public class CustomExporterTab extends AbstractPreferenceTabView extensionColumn; public CustomExporterTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -37,7 +36,9 @@ 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()); diff --git a/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java b/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java index e0db96a69bf9..9d1be875eaea 100644 --- a/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/customexporter/CustomExporterTabViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.preferences.customexporter; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; @@ -15,10 +11,16 @@ import org.jabref.logic.exporter.TemplateExporter; import org.jabref.logic.preferences.CliPreferences; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + 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; @@ -30,7 +32,8 @@ public CustomExporterTabViewModel(CliPreferences preferences, DialogService dial @Override public void setValues() { - List exportersLogic = preferences.getExportPreferences().getCustomExporters(); + List exportersLogic = + preferences.getExportPreferences().getCustomExporters(); exporters.clear(); for (TemplateExporter exporter : exportersLogic) { exporters.add(new ExporterViewModel(exporter)); @@ -39,9 +42,8 @@ public void setValues() { @Override public void storeSettings() { - List exportersLogic = exporters.stream() - .map(ExporterViewModel::getLogic) - .collect(Collectors.toList()); + List exportersLogic = + exporters.stream().map(ExporterViewModel::getLogic).collect(Collectors.toList()); preferences.getExportPreferences().setCustomExporters(exportersLogic); } @@ -59,7 +61,8 @@ public void modifyExporter() { } ExporterViewModel exporterToModify = selectedExporters.getFirst(); - CreateModifyExporterDialogView dialog = new CreateModifyExporterDialogView(exporterToModify); + CreateModifyExporterDialogView dialog = + new CreateModifyExporterDialogView(exporterToModify); Optional exporter = dialogService.showCustomDialogAndWait(dialog); if ((exporter != null) && exporter.isPresent()) { exporters.remove(exporterToModify); diff --git a/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java b/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java index 7a05fcdc9db9..9db5c0ac43e6 100644 --- a/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java +++ b/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTab.java @@ -1,5 +1,8 @@ package org.jabref.gui.preferences.customimporter; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.SelectionMode; @@ -13,10 +16,8 @@ import org.jabref.gui.util.ViewModelTableRowFactory; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; - -public class CustomImporterTab extends AbstractPreferenceTabView implements PreferencesTab { +public class CustomImporterTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TableView importerTable; @FXML private TableColumn nameColumn; @@ -25,9 +26,7 @@ public class CustomImporterTab extends AbstractPreferenceTabView cellData.getValue().name()); classColumn.setCellValueFactory(cellData -> cellData.getValue().className()); basePathColumn.setCellValueFactory(cellData -> cellData.getValue().basePath()); @@ -49,9 +50,13 @@ private void initialize() { .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 diff --git a/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java b/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java index 227fe4a1e729..61c62b442c5c 100644 --- a/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/customimporter/CustomImporterTabViewModel.java @@ -1,12 +1,5 @@ package org.jabref.gui.preferences.customimporter; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; @@ -21,16 +14,24 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.StandardFileType; import org.jabref.logic.util.io.FileUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + 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; @@ -42,7 +43,8 @@ public CustomImporterTabViewModel(CliPreferences preferences, DialogService dial @Override public void setValues() { - Set importersLogic = preferences.getImporterPreferences().getCustomImporters(); + Set importersLogic = + preferences.getImporterPreferences().getCustomImporters(); importers.clear(); for (CustomImporter importer : importersLogic) { importers.add(new ImporterViewModel(importer)); @@ -51,9 +53,12 @@ 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())); } /** @@ -64,7 +69,8 @@ public void storeSettings() { * @return class name */ private static String pathToClass(String basePath, Path path) { - String className = FileUtil.relativize(path, Collections.singletonList(Path.of(basePath))).toString(); + String className = + FileUtil.relativize(path, Collections.singletonList(Path.of(basePath))).toString(); if (className != null) { int lastDot = className.lastIndexOf('.'); if (lastDot < 0) { @@ -76,39 +82,59 @@ 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(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .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(); + boolean isArchive = + FileUtil.getFileExtension(selectedFile.get()) + .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) { 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?"), + 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"), - exc); + Localization.lang("Could not instantiate %0 %1", "importer"), exc); } } else { try { @@ -119,11 +145,16 @@ public void addImporter() { importers.add(new ImporterViewModel(importer)); } catch (Exception exc) { LOGGER.error("Could not instantiate importer", exc); - dialogService.showErrorDialogAndWait(Localization.lang("Could not instantiate %0", selectedFile.get().toString()), 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); } } diff --git a/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java b/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java index e2202142cbd3..89a8ea453aad 100644 --- a/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java +++ b/src/main/java/org/jabref/gui/preferences/entry/EntryTab.java @@ -1,6 +1,6 @@ package org.jabref.gui.preferences.entry; -import java.util.function.UnaryOperator; +import com.airhacks.afterburner.views.ViewLoader; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -16,11 +16,10 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - -public class EntryTab extends AbstractPreferenceTabView implements PreferencesTab { - +import java.util.function.UnaryOperator; +public class EntryTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TextField keywordSeparator; @@ -37,9 +36,7 @@ public class EntryTab extends AbstractPreferenceTabView imple @FXML private CheckBox addModificationDate; public EntryTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } public void initialize() { @@ -47,32 +44,46 @@ public void initialize() { keywordSeparator.textProperty().bindBidirectional(viewModel.keywordSeparatorProperty()); - // 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; - } - return null; // null means the change is rejected - }; + // 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; + } + return null; // null means the change is rejected + }; TextFormatter formatter = new TextFormatter<>(singleCharacterFilter); keywordSeparator.setTextFormatter(formatter); resolveStrings.selectedProperty().bindBidirectional(viewModel.resolveStringsProperty()); - resolveStringsForFields.textProperty().bindBidirectional(viewModel.resolveStringsForFieldsProperty()); + resolveStringsForFields + .textProperty() + .bindBidirectional(viewModel.resolveStringsForFieldsProperty()); nonWrappableFields.textProperty().bindBidirectional(viewModel.nonWrappableFieldsProperty()); markOwner.selectedProperty().bindBidirectional(viewModel.markOwnerProperty()); markOwnerName.textProperty().bindBidirectional(viewModel.markOwnerNameProperty()); markOwnerName.disableProperty().bind(markOwner.selectedProperty().not()); - markOwnerOverwrite.selectedProperty().bindBidirectional(viewModel.markOwnerOverwriteProperty()); + markOwnerOverwrite + .selectedProperty() + .bindBidirectional(viewModel.markOwnerOverwriteProperty()); markOwnerOverwrite.disableProperty().bind(markOwner.selectedProperty().not()); addCreationDate.selectedProperty().bindBidirectional(viewModel.addCreationDateProperty()); - addModificationDate.selectedProperty().bindBidirectional(viewModel.addModificationDateProperty()); + 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); } @Override diff --git a/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java b/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java index 2f6fbbe9aa01..f38682cdd4af 100644 --- a/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/entry/EntryTabViewModel.java @@ -44,8 +44,10 @@ public void setValues() { keywordSeparatorProperty.setValue(bibEntryPreferences.getKeywordSeparator().toString()); resolveStringsProperty.setValue(fieldPreferences.shouldResolveStrings()); - resolveStringsForFieldsProperty.setValue(FieldFactory.serializeFieldsList(fieldPreferences.getResolvableFields())); - nonWrappableFieldsProperty.setValue(FieldFactory.serializeFieldsList(fieldPreferences.getNonWrappableFields())); + resolveStringsForFieldsProperty.setValue( + FieldFactory.serializeFieldsList(fieldPreferences.getResolvableFields())); + nonWrappableFieldsProperty.setValue( + FieldFactory.serializeFieldsList(fieldPreferences.getNonWrappableFields())); markOwnerProperty.setValue(ownerPreferences.isUseOwner()); markOwnerNameProperty.setValue(ownerPreferences.getDefaultOwner()); @@ -57,11 +59,15 @@ public void setValues() { @Override public void storeSettings() { - bibEntryPreferences.keywordSeparatorProperty().setValue(keywordSeparatorProperty.getValue().charAt(0)); + bibEntryPreferences + .keywordSeparatorProperty() + .setValue(keywordSeparatorProperty.getValue().charAt(0)); fieldPreferences.setResolveStrings(resolveStringsProperty.getValue()); - fieldPreferences.setResolvableFields(FieldFactory.parseFieldList(resolveStringsForFieldsProperty.getValue().trim())); - fieldPreferences.setNonWrappableFields(FieldFactory.parseFieldList(nonWrappableFieldsProperty.getValue().trim())); + fieldPreferences.setResolvableFields( + FieldFactory.parseFieldList(resolveStringsForFieldsProperty.getValue().trim())); + fieldPreferences.setNonWrappableFields( + FieldFactory.parseFieldList(nonWrappableFieldsProperty.getValue().trim())); ownerPreferences.setUseOwner(markOwnerProperty.getValue()); ownerPreferences.setDefaultOwner(markOwnerNameProperty.getValue()); diff --git a/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java b/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java index bee167050a80..a9a44bd2cd25 100644 --- a/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java +++ b/src/main/java/org/jabref/gui/preferences/entryeditor/EntryEditorTab.java @@ -1,5 +1,7 @@ package org.jabref.gui.preferences.entryeditor; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; @@ -13,9 +15,8 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - -public class EntryEditorTab extends AbstractPreferenceTabView implements PreferencesTab { +public class EntryEditorTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private CheckBox openOnNewEntry; @FXML private CheckBox defaultSource; @@ -35,9 +36,7 @@ public class EntryEditorTab extends AbstractPreferenceTabView> tabNamesAndFields) { public void storeSettings() { // entryEditorPreferences.setEntryEditorTabList(); entryEditorPreferences.setShouldOpenOnNewEntry(openOnNewEntryProperty.getValue()); - entryEditorPreferences.setShouldShowRecommendationsTab(enableRelatedArticlesTabProperty.getValue()); + entryEditorPreferences.setShouldShowRecommendationsTab( + enableRelatedArticlesTabProperty.getValue()); entryEditorPreferences.setShouldShowAiSummaryTab(enableAiSummaryTabProperty.getValue()); entryEditorPreferences.setShouldShowAiChatTab(enableAiChatTabProperty.getValue()); mrDlibPreferences.setAcceptRecommendations(acceptRecommendationsProperty.getValue()); - entryEditorPreferences.setShouldShowLatexCitationsTab(enableLatexCitationsTabProperty.getValue()); + entryEditorPreferences.setShouldShowLatexCitationsTab( + enableLatexCitationsTabProperty.getValue()); 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()); @@ -117,19 +125,23 @@ public void storeSettings() { if (parts.length != 2) { dialogService.showInformationDialogAndWait( Localization.lang("Error"), - Localization.lang("Each line must be of the following form: 'tab:field1;field2;...;fieldN'.")); + 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 + // 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 + // 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).", + Localization.lang( + "Field names are not allowed to contain white spaces or certain characters (%0).", "# { } ( ) ~ , ^ & - \" ' ` ʹ \\")); return; } diff --git a/src/main/java/org/jabref/gui/preferences/export/ExportTab.java b/src/main/java/org/jabref/gui/preferences/export/ExportTab.java index 3e99e6153409..0c4f46c7c12d 100644 --- a/src/main/java/org/jabref/gui/preferences/export/ExportTab.java +++ b/src/main/java/org/jabref/gui/preferences/export/ExportTab.java @@ -1,5 +1,7 @@ package org.jabref.gui.preferences.export; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import org.jabref.gui.commonfxcontrols.SaveOrderConfigPanel; @@ -7,15 +9,12 @@ import org.jabref.gui.preferences.PreferencesTab; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - -public class ExportTab extends AbstractPreferenceTabView implements PreferencesTab { +public class ExportTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private SaveOrderConfigPanel exportOrderPanel; public ExportTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -26,9 +25,15 @@ public String getTabName() { public void initialize() { this.viewModel = new ExportTabViewModel(preferences.getExportPreferences()); - exportOrderPanel.saveInOriginalProperty().bindBidirectional(viewModel.saveInOriginalProperty()); - exportOrderPanel.saveInTableOrderProperty().bindBidirectional(viewModel.saveInTableOrderProperty()); - exportOrderPanel.saveInSpecifiedOrderProperty().bindBidirectional(viewModel.saveInSpecifiedOrderProperty()); + exportOrderPanel + .saveInOriginalProperty() + .bindBidirectional(viewModel.saveInOriginalProperty()); + exportOrderPanel + .saveInTableOrderProperty() + .bindBidirectional(viewModel.saveInTableOrderProperty()); + exportOrderPanel + .saveInSpecifiedOrderProperty() + .bindBidirectional(viewModel.saveInSpecifiedOrderProperty()); exportOrderPanel.sortableFieldsProperty().bind(viewModel.sortableFieldsProperty()); exportOrderPanel.sortCriteriaProperty().bindBidirectional(viewModel.sortCriteriaProperty()); exportOrderPanel.setCriteriaLimit(3); diff --git a/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java b/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java index 00717280eaf3..612acedbd0b1 100644 --- a/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/export/ExportTabViewModel.java @@ -1,8 +1,5 @@ package org.jabref.gui.preferences.export; -import java.util.ArrayList; -import java.util.Set; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -17,14 +14,19 @@ import org.jabref.model.entry.field.InternalField; import org.jabref.model.metadata.SaveOrder; +import java.util.ArrayList; +import java.util.Set; + 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 +42,10 @@ 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); @@ -54,9 +57,14 @@ public void setValues() { @Override public void storeSettings() { - SaveOrder newSaveOrder = new SaveOrder( - SaveOrder.OrderType.fromBooleans(exportInSpecifiedOrderProperty.getValue(), exportInOriginalProperty.getValue()), - sortCriteriaProperty.stream().map(SortCriterionViewModel::getCriterion).toList()); + SaveOrder newSaveOrder = + new SaveOrder( + SaveOrder.OrderType.fromBooleans( + exportInSpecifiedOrderProperty.getValue(), + exportInOriginalProperty.getValue()), + sortCriteriaProperty.stream() + .map(SortCriterionViewModel::getCriterion) + .toList()); exportPreferences.setExportSaveOrder(newSaveOrder); } diff --git a/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java b/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java index 00cf98349249..967313e4fb31 100644 --- a/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java +++ b/src/main/java/org/jabref/gui/preferences/external/ExternalTab.java @@ -1,5 +1,9 @@ package org.jabref.gui.preferences.external; +import com.airhacks.afterburner.views.ViewLoader; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -18,10 +22,8 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - -public class ExternalTab extends AbstractPreferenceTabView implements PreferencesTab { +public class ExternalTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TextField eMailReferenceSubject; @FXML private CheckBox autoOpenAttachedFolders; @@ -40,9 +42,7 @@ public class ExternalTab extends AbstractPreferenceTabView private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public ExternalTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -58,33 +58,61 @@ public void initialize() { .withIcon(PushToApplication::getApplicationIcon) .install(pushToApplicationCombo); - eMailReferenceSubject.textProperty().bindBidirectional(viewModel.eMailReferenceSubjectProperty()); - autoOpenAttachedFolders.selectedProperty().bindBidirectional(viewModel.autoOpenAttachedFoldersProperty()); + eMailReferenceSubject + .textProperty() + .bindBidirectional(viewModel.eMailReferenceSubjectProperty()); + autoOpenAttachedFolders + .selectedProperty() + .bindBidirectional(viewModel.autoOpenAttachedFoldersProperty()); pushToApplicationCombo.itemsProperty().bind(viewModel.pushToApplicationsListProperty()); - pushToApplicationCombo.valueProperty().bindBidirectional(viewModel.selectedPushToApplication()); + pushToApplicationCombo + .valueProperty() + .bindBidirectional(viewModel.selectedPushToApplication()); citeCommand.textProperty().bindBidirectional(viewModel.citeCommandProperty()); - useCustomTerminal.selectedProperty().bindBidirectional(viewModel.useCustomTerminalProperty()); - customTerminalCommand.textProperty().bindBidirectional(viewModel.customTerminalCommandProperty()); + useCustomTerminal + .selectedProperty() + .bindBidirectional(viewModel.useCustomTerminalProperty()); + customTerminalCommand + .textProperty() + .bindBidirectional(viewModel.customTerminalCommandProperty()); customTerminalCommand.disableProperty().bind(useCustomTerminal.selectedProperty().not()); customTerminalBrowse.disableProperty().bind(useCustomTerminal.selectedProperty().not()); - useCustomFileBrowser.selectedProperty().bindBidirectional(viewModel.useCustomFileBrowserProperty()); - customFileBrowserCommand.textProperty().bindBidirectional(viewModel.customFileBrowserCommandProperty()); - customFileBrowserCommand.disableProperty().bind(useCustomFileBrowser.selectedProperty().not()); - customFileBrowserBrowse.disableProperty().bind(useCustomFileBrowser.selectedProperty().not()); + useCustomFileBrowser + .selectedProperty() + .bindBidirectional(viewModel.useCustomFileBrowserProperty()); + customFileBrowserCommand + .textProperty() + .bindBidirectional(viewModel.customFileBrowserCommandProperty()); + customFileBrowserCommand + .disableProperty() + .bind(useCustomFileBrowser.selectedProperty().not()); + customFileBrowserBrowse + .disableProperty() + .bind(useCustomFileBrowser.selectedProperty().not()); kindleEmail.textProperty().bindBidirectional(viewModel.kindleEmailProperty()); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> { - validationVisualizer.initVisualization(viewModel.terminalCommandValidationStatus(), customTerminalCommand); - validationVisualizer.initVisualization(viewModel.fileBrowserCommandValidationStatus(), customFileBrowserCommand); - }); + Platform.runLater( + () -> { + validationVisualizer.initVisualization( + viewModel.terminalCommandValidationStatus(), customTerminalCommand); + 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 diff --git a/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java b/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java index 815009515a9f..726645820e63 100644 --- a/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/external/ExternalTabViewModel.java @@ -1,6 +1,10 @@ package org.jabref.gui.preferences.external; -import java.util.HashMap; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -28,18 +32,16 @@ import org.jabref.logic.push.CitationCommandString; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.HashMap; 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 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(""); @@ -53,7 +55,8 @@ public class ExternalTabViewModel implements PreferenceTabViewModel { private final DialogService dialogService; private final GuiPreferences preferences; - private final FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder().build(); + private final FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder().build(); private final ExternalApplicationsPreferences initialExternalApplicationPreferences; private final PushToApplicationPreferences initialPushToApplicationPreferences; @@ -62,65 +65,93 @@ public class ExternalTabViewModel implements PreferenceTabViewModel { public ExternalTabViewModel(DialogService dialogService, GuiPreferences preferences) { this.dialogService = dialogService; this.preferences = preferences; - this.initialExternalApplicationPreferences = this.preferences.getExternalApplicationsPreferences(); - this.initialPushToApplicationPreferences = this.preferences.getPushToApplicationPreferences(); - this.workingPushToApplicationPreferences = new PushToApplicationPreferences( - initialPushToApplicationPreferences.getActiveApplicationName(), - new HashMap<>(initialPushToApplicationPreferences.getCommandPaths()), - initialPushToApplicationPreferences.getEmacsArguments(), - initialPushToApplicationPreferences.getVimServer()); - - terminalCommandValidator = new FunctionBasedValidator<>( - customTerminalCommandProperty, - input -> !StringUtil.isNullOrEmpty(input), - 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, - input -> !StringUtil.isNullOrEmpty(input), - ValidationMessage.error("%s > %s %n %n %s".formatted( - Localization.lang("External programs"), - Localization.lang("Custom applications"), - Localization.lang("Please specify a file browser.")))); + this.initialExternalApplicationPreferences = + this.preferences.getExternalApplicationsPreferences(); + this.initialPushToApplicationPreferences = + this.preferences.getPushToApplicationPreferences(); + this.workingPushToApplicationPreferences = + new PushToApplicationPreferences( + initialPushToApplicationPreferences.getActiveApplicationName(), + new HashMap<>(initialPushToApplicationPreferences.getCommandPaths()), + initialPushToApplicationPreferences.getEmacsArguments(), + initialPushToApplicationPreferences.getVimServer()); + + terminalCommandValidator = + new FunctionBasedValidator<>( + customTerminalCommandProperty, + input -> !StringUtil.isNullOrEmpty(input), + 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, + input -> !StringUtil.isNullOrEmpty(input), + ValidationMessage.error( + "%s > %s %n %n %s" + .formatted( + Localization.lang("External programs"), + Localization.lang("Custom applications"), + Localization.lang( + "Please specify a file browser.")))); } @Override public void setValues() { - eMailReferenceSubjectProperty.setValue(initialExternalApplicationPreferences.getEmailSubject()); - autoOpenAttachedFoldersProperty.setValue(initialExternalApplicationPreferences.shouldAutoOpenEmailAttachmentsFolder()); + eMailReferenceSubjectProperty.setValue( + initialExternalApplicationPreferences.getEmailSubject()); + autoOpenAttachedFoldersProperty.setValue( + initialExternalApplicationPreferences.shouldAutoOpenEmailAttachmentsFolder()); pushToApplicationsListProperty.setValue( - FXCollections.observableArrayList(PushToApplications.getAllApplications(dialogService, preferences))); + FXCollections.observableArrayList( + PushToApplications.getAllApplications(dialogService, preferences))); selectedPushToApplicationProperty.setValue( - PushToApplications.getApplicationByName(initialPushToApplicationPreferences.getActiveApplicationName(), dialogService, preferences) - .orElse(new PushToEmacs(dialogService, preferences))); - - citeCommandProperty.setValue(initialExternalApplicationPreferences.getCiteCommand().toString()); - - useCustomTerminalProperty.setValue(initialExternalApplicationPreferences.useCustomTerminal()); - customTerminalCommandProperty.setValue(initialExternalApplicationPreferences.getCustomTerminalCommand()); - useCustomFileBrowserProperty.setValue(initialExternalApplicationPreferences.useCustomFileBrowser()); - customFileBrowserCommandProperty.setValue(initialExternalApplicationPreferences.getCustomFileBrowserCommand()); + PushToApplications.getApplicationByName( + initialPushToApplicationPreferences.getActiveApplicationName(), + dialogService, + preferences) + .orElse(new PushToEmacs(dialogService, preferences))); + + citeCommandProperty.setValue( + initialExternalApplicationPreferences.getCiteCommand().toString()); + + useCustomTerminalProperty.setValue( + initialExternalApplicationPreferences.useCustomTerminal()); + customTerminalCommandProperty.setValue( + initialExternalApplicationPreferences.getCustomTerminalCommand()); + useCustomFileBrowserProperty.setValue( + initialExternalApplicationPreferences.useCustomFileBrowser()); + customFileBrowserCommandProperty.setValue( + initialExternalApplicationPreferences.getCustomFileBrowserCommand()); kindleEmailProperty.setValue(initialExternalApplicationPreferences.getKindleEmail()); } @Override public void storeSettings() { - ExternalApplicationsPreferences externalPreferences = preferences.getExternalApplicationsPreferences(); + ExternalApplicationsPreferences externalPreferences = + preferences.getExternalApplicationsPreferences(); externalPreferences.setEMailSubject(eMailReferenceSubjectProperty.getValue()); - externalPreferences.setAutoOpenEmailAttachmentsFolder(autoOpenAttachedFoldersProperty.getValue()); - externalPreferences.setCiteCommand(CitationCommandString.from(citeCommandProperty.getValue())); + externalPreferences.setAutoOpenEmailAttachmentsFolder( + autoOpenAttachedFoldersProperty.getValue()); + externalPreferences.setCiteCommand( + CitationCommandString.from(citeCommandProperty.getValue())); externalPreferences.setUseCustomTerminal(useCustomTerminalProperty.getValue()); externalPreferences.setCustomTerminalCommand(customTerminalCommandProperty.getValue()); externalPreferences.setUseCustomFileBrowser(useCustomFileBrowserProperty.getValue()); - externalPreferences.setCustomFileBrowserCommand(customFileBrowserCommandProperty.getValue()); + externalPreferences.setCustomFileBrowserCommand( + customFileBrowserCommandProperty.getValue()); externalPreferences.setKindleEmail(kindleEmailProperty.getValue()); - PushToApplicationPreferences pushPreferences = preferences.getPushToApplicationPreferences(); - pushPreferences.setActiveApplicationName(selectedPushToApplicationProperty.getValue().getDisplayName()); + PushToApplicationPreferences pushPreferences = + preferences.getPushToApplicationPreferences(); + pushPreferences.setActiveApplicationName( + selectedPushToApplicationProperty.getValue().getDisplayName()); pushPreferences.setCommandPaths(workingPushToApplicationPreferences.getCommandPaths()); pushPreferences.setEmacsArguments(workingPushToApplicationPreferences.getEmacsArguments()); pushPreferences.setVimServer(workingPushToApplicationPreferences.getVimServer()); @@ -148,8 +179,10 @@ 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; @@ -157,31 +190,43 @@ public boolean validateSettings() { public void pushToApplicationSettings() { PushToApplication selectedApplication = selectedPushToApplicationProperty.getValue(); - PushToApplicationSettings settings = selectedApplication.getSettings(selectedApplication, workingPushToApplicationPreferences); + PushToApplicationSettings settings = + selectedApplication.getSettings( + selectedApplication, 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())); + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + file -> + customTerminalCommandProperty.setValue( + file.toAbsolutePath().toString())); } public void customFileBrowserBrowse() { - dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(file -> customFileBrowserCommandProperty.setValue(file.toAbsolutePath().toString())); + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + file -> + customFileBrowserCommandProperty.setValue( + file.toAbsolutePath().toString())); } // EMail @@ -231,6 +276,10 @@ public StringProperty customFileBrowserCommandProperty() { } public void resetCiteCommandToDefault() { - this.citeCommandProperty.setValue(preferences.getExternalApplicationsPreferences().getDefaultCiteCommand().toString()); + this.citeCommandProperty.setValue( + preferences + .getExternalApplicationsPreferences() + .getDefaultCiteCommand() + .toString()); } } diff --git a/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java b/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java index 44cab5420aa2..e7a58725067d 100644 --- a/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java +++ b/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeEntryDialog.java @@ -1,5 +1,11 @@ package org.jabref.gui.preferences.externalfiletypes; +import com.airhacks.afterburner.views.ViewLoader; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; + import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.event.ActionEvent; @@ -17,10 +23,6 @@ import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.gui.util.IconValidationDecorator; -import com.airhacks.afterburner.views.ViewLoader; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; - public class EditExternalFileTypeEntryDialog extends BaseDialog { @FXML private RadioButton defaultApplication; @@ -34,7 +36,10 @@ public class EditExternalFileTypeEntryDialog extends BaseDialog { @FXML private Label icon; @Inject private DialogService dialogService; - private final FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder().withInitialDirectory(NativeDesktop.get().getApplicationDirectory()).build(); + private final FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .withInitialDirectory(NativeDesktop.get().getApplicationDirectory()) + .build(); private final ExternalFileTypeItemViewModel item; private final ObservableList fileTypes; @@ -42,25 +47,29 @@ public class EditExternalFileTypeEntryDialog extends BaseDialog { 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); final Button confirmDialogButton = (Button) getDialogPane().lookupButton(ButtonType.OK); - confirmDialogButton.disableProperty().bind(viewModel.validationStatus().validProperty().not()); - this.setResultConverter(button -> { - if (button == ButtonType.OK) { - viewModel.storeSettings(); - } - return null; - }); + confirmDialogButton + .disableProperty() + .bind(viewModel.validationStatus().validProperty().not()); + this.setResultConverter( + button -> { + if (button == ButtonType.OK) { + viewModel.storeSettings(); + } + return null; + }); } @FXML @@ -71,24 +80,37 @@ public void initialize() { icon.setGraphic(viewModel.getIcon()); - defaultApplication.selectedProperty().bindBidirectional(viewModel.defaultApplicationSelectedProperty()); - customApplication.selectedProperty().bindBidirectional(viewModel.customApplicationSelectedProperty()); + defaultApplication + .selectedProperty() + .bindBidirectional(viewModel.defaultApplicationSelectedProperty()); + customApplication + .selectedProperty() + .bindBidirectional(viewModel.customApplicationSelectedProperty()); selectedApplication.disableProperty().bind(viewModel.defaultApplicationSelectedProperty()); btnBrowse.disableProperty().bind(viewModel.defaultApplicationSelectedProperty()); extension.textProperty().bindBidirectional(viewModel.extensionProperty()); name.textProperty().bindBidirectional(viewModel.nameProperty()); mimeType.textProperty().bindBidirectional(viewModel.mimeTypeProperty()); - selectedApplication.textProperty().bindBidirectional(viewModel.selectedApplicationProperty()); - - Platform.runLater(() -> { - visualizer.initVisualization(viewModel.extensionValidation(), extension, true); - visualizer.initVisualization(viewModel.nameValidation(), name, true); - visualizer.initVisualization(viewModel.mimeTypeValidation(), mimeType, true); - }); + selectedApplication + .textProperty() + .bindBidirectional(viewModel.selectedApplicationProperty()); + + Platform.runLater( + () -> { + visualizer.initVisualization(viewModel.extensionValidation(), extension, true); + visualizer.initVisualization(viewModel.nameValidation(), name, true); + visualizer.initVisualization(viewModel.mimeTypeValidation(), mimeType, true); + }); } @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/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java b/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java index e56aa5e51146..f7ae459a2005 100644 --- a/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/externalfiletypes/EditExternalFileTypeViewModel.java @@ -1,5 +1,11 @@ package org.jabref.gui.preferences.externalfiletypes; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -10,20 +16,16 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; - 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 BooleanProperty defaultApplicationSelectedProperty = + new SimpleBooleanProperty(false); + private final BooleanProperty customApplicationSelectedProperty = + new SimpleBooleanProperty(false); private final ObservableList fileTypes; private final String originalExtension; private final String originalName; @@ -33,7 +35,9 @@ public class EditExternalFileTypeViewModel { 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(); @@ -47,7 +51,8 @@ public EditExternalFileTypeViewModel(ExternalFileTypeItemViewModel fileTypeViewM defaultApplicationSelectedProperty.setValue(true); } else { customApplicationSelectedProperty.setValue(true); - selectedApplicationProperty.setValue(fileTypeViewModel.applicationProperty().getValue()); + selectedApplicationProperty.setValue( + fileTypeViewModel.applicationProperty().getValue()); } setupValidation(); @@ -57,68 +62,86 @@ 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() { @@ -182,7 +205,8 @@ public void storeSettings() { 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(""); diff --git a/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java b/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java index 1529d07d92cb..cc49ee72193d 100644 --- a/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java +++ b/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTab.java @@ -1,5 +1,7 @@ package org.jabref.gui.preferences.externalfiletypes; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -12,26 +14,26 @@ import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - /** * 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 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 @@ -41,31 +43,38 @@ public String getTabName() { @FXML public void initialize() { - viewModel = new ExternalFileTypesTabViewModel(preferences.getExternalApplicationsPreferences(), dialogService); + viewModel = + new ExternalFileTypesTabViewModel( + preferences.getExternalApplicationsPreferences(), dialogService); fileTypesTable.setItems(viewModel.getFileTypes()); - fileTypesTableIconColumn.setCellValueFactory(cellData -> cellData.getValue().iconProperty()); + fileTypesTableIconColumn.setCellValueFactory( + cellData -> cellData.getValue().iconProperty()); new ValueTableCellFactory() .withGraphic(JabRefIcon::getGraphicNode) .install(fileTypesTableIconColumn); - fileTypesTableNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + fileTypesTableNameColumn.setCellValueFactory( + cellData -> cellData.getValue().nameProperty()); new ValueTableCellFactory() .withText(name -> name) .install(fileTypesTableNameColumn); - fileTypesTableExtensionColumn.setCellValueFactory(cellData -> cellData.getValue().extensionProperty()); + fileTypesTableExtensionColumn.setCellValueFactory( + cellData -> cellData.getValue().extensionProperty()); new ValueTableCellFactory() .withText(extension -> extension) .install(fileTypesTableExtensionColumn); - fileTypesTableMimeTypeColumn.setCellValueFactory(cellData -> cellData.getValue().mimetypeProperty()); + fileTypesTableMimeTypeColumn.setCellValueFactory( + cellData -> cellData.getValue().mimetypeProperty()); new ValueTableCellFactory() .withText(mimetype -> mimetype) .install(fileTypesTableMimeTypeColumn); - fileTypesTableApplicationColumn.setCellValueFactory(cellData -> cellData.getValue().applicationProperty()); + fileTypesTableApplicationColumn.setCellValueFactory( + cellData -> cellData.getValue().applicationProperty()); new ValueTableCellFactory() .withText(extension -> extension) .install(fileTypesTableApplicationColumn); diff --git a/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java b/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java index a0c4866fc2e6..1721cbf6167f 100644 --- a/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/externalfiletypes/ExternalFileTypesTabViewModel.java @@ -1,9 +1,5 @@ package org.jabref.gui.preferences.externalfiletypes; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; - import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -13,19 +9,26 @@ import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.gui.preferences.PreferenceTabViewModel; import org.jabref.logic.l10n.Localization; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + public class ExternalFileTypesTabViewModel implements PreferenceTabViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(ExternalFileTypesTabViewModel.class); - private final ObservableList fileTypes = FXCollections.observableArrayList(); + 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,34 @@ 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)); } @@ -75,11 +84,13 @@ public ObservableList getFileTypes() { } protected void showEditDialog(ExternalFileTypeItemViewModel item, String dialogTitle) { - dialogService.showCustomDialogAndWait(new EditExternalFileTypeEntryDialog(item, dialogTitle, fileTypes)); + dialogService.showCustomDialogAndWait( + new EditExternalFileTypeEntryDialog(item, dialogTitle, fileTypes)); } public boolean edit(ExternalFileTypeItemViewModel type) { - ExternalFileTypeItemViewModel typeToModify = new ExternalFileTypeItemViewModel(type.toExternalFileType()); + ExternalFileTypeItemViewModel typeToModify = + new ExternalFileTypeItemViewModel(type.toExternalFileType()); showEditDialog(typeToModify, Localization.lang("Edit file type")); if (!isValidExternalFileType(typeToModify)) { @@ -110,7 +121,9 @@ public boolean isValidExternalFileType(ExternalFileTypeItemViewModel item) { } private boolean withEmptyValue(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) { diff --git a/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java b/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java index e178b10a6e76..7f46ba947dfb 100644 --- a/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java +++ b/src/main/java/org/jabref/gui/preferences/general/GeneralTab.java @@ -1,6 +1,11 @@ package org.jabref.gui.preferences.general; -import java.util.regex.Pattern; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; @@ -28,12 +33,10 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; +import java.util.regex.Pattern; -public class GeneralTab extends AbstractPreferenceTabView implements PreferencesTab { +public class GeneralTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private ComboBox language; @FXML private ComboBox theme; @@ -61,20 +64,22 @@ public class GeneralTab extends AbstractPreferenceTabView i 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 @@ -83,7 +88,9 @@ public String getTabName() { } public void initialize() { - this.viewModel = new GeneralTabViewModel(dialogService, preferences, fileUpdateMonitor, entryTypesManager); + this.viewModel = + new GeneralTabViewModel( + dialogService, preferences, fileUpdateMonitor, entryTypesManager); new ViewModelListCellFactory() .withText(Language::getDisplayName) @@ -107,17 +114,23 @@ public void initialize() { theme.valueProperty().bindBidirectional(viewModel.selectedThemeProperty()); themeSyncOs.selectedProperty().bindBidirectional(viewModel.themeSyncOsProperty()); customThemePath.textProperty().bindBidirectional(viewModel.customPathToThemeProperty()); - EasyBind.subscribe(viewModel.selectedThemeProperty(), theme -> { - boolean isCustomTheme = theme == ThemeTypes.CUSTOM; - customThemePath.disableProperty().set(!isCustomTheme); - customThemeBrowse.disableProperty().set(!isCustomTheme); - }); + EasyBind.subscribe( + viewModel.selectedThemeProperty(), + theme -> { + boolean isCustomTheme = theme == ThemeTypes.CUSTOM; + customThemePath.disableProperty().set(!isCustomTheme); + customThemeBrowse.disableProperty().set(!isCustomTheme); + }); validationVisualizer.setDecoration(new IconValidationDecorator()); openLastStartup.selectedProperty().bindBidirectional(viewModel.openLastStartupProperty()); - showAdvancedHints.selectedProperty().bindBidirectional(viewModel.showAdvancedHintsProperty()); - inspectionWarningDuplicate.selectedProperty().bindBidirectional(viewModel.inspectionWarningDuplicateProperty()); + showAdvancedHints + .selectedProperty() + .bindBidirectional(viewModel.showAdvancedHintsProperty()); + inspectionWarningDuplicate + .selectedProperty() + .bindBidirectional(viewModel.inspectionWarningDuplicateProperty()); confirmDelete.selectedProperty().bindBidirectional(viewModel.confirmDeleteProperty()); new ViewModelListCellFactory() @@ -126,21 +139,41 @@ public void initialize() { biblatexMode.itemsProperty().bind(viewModel.biblatexModeListProperty()); biblatexMode.valueProperty().bindBidirectional(viewModel.selectedBiblatexModeProperty()); - alwaysReformatBib.selectedProperty().bindBidirectional(viewModel.alwaysReformatBibProperty()); - autosaveLocalLibraries.selectedProperty().bindBidirectional(viewModel.autosaveLocalLibrariesProperty()); + 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()); backupDirectory.disableProperty().bind(viewModel.createBackupProperty().not()); - Platform.runLater(() -> { - validationVisualizer.initVisualization(viewModel.remotePortValidationStatus(), remotePort); - validationVisualizer.initVisualization(viewModel.fontSizeValidationStatus(), fontSize); - validationVisualizer.initVisualization(viewModel.customPathToThemeValidationStatus(), customThemePath); - }); + Platform.runLater( + () -> { + validationVisualizer.initVisualization( + viewModel.remotePortValidationStatus(), remotePort); + validationVisualizer.initVisualization( + viewModel.fontSizeValidationStatus(), fontSize); + validationVisualizer.initVisualization( + viewModel.customPathToThemeValidationStatus(), customThemePath); + }); remoteServer.selectedProperty().bindBidirectional(viewModel.remoteServerProperty()); remotePort.textProperty().bindBidirectional(viewModel.remotePortProperty()); diff --git a/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java b/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java index 460d1aae3c7c..77e0b483f112 100644 --- a/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java @@ -1,10 +1,12 @@ package org.jabref.gui.preferences.general; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import com.airhacks.afterburner.injection.Injector; + +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -44,12 +46,11 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.injection.Injector; -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; public class GeneralTabViewModel implements PreferenceTabViewModel { @@ -77,8 +78,10 @@ public class GeneralTabViewModel implements PreferenceTabViewModel { private final BooleanProperty inspectionWarningDuplicateProperty = new SimpleBooleanProperty(); private final BooleanProperty confirmDeleteProperty = new SimpleBooleanProperty(); - private final ListProperty bibliographyModeListProperty = new SimpleListProperty<>(); - private final ObjectProperty selectedBiblatexModeProperty = new SimpleObjectProperty<>(); + private final ListProperty bibliographyModeListProperty = + new SimpleListProperty<>(); + private final ObjectProperty selectedBiblatexModeProperty = + new SimpleObjectProperty<>(); private final BooleanProperty alwaysReformatBibProperty = new SimpleBooleanProperty(); private final BooleanProperty autosaveLocalLibraries = new SimpleBooleanProperty(); @@ -104,7 +107,11 @@ public class GeneralTabViewModel implements PreferenceTabViewModel { private final BibEntryTypesManager entryTypesManager; private final TrustStoreManager trustStoreManager; - public GeneralTabViewModel(DialogService dialogService, GuiPreferences preferences, FileUpdateMonitor fileUpdateMonitor, BibEntryTypesManager entryTypesManager) { + public GeneralTabViewModel( + DialogService dialogService, + GuiPreferences preferences, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) { this.dialogService = dialogService; this.preferences = preferences; this.workspacePreferences = preferences.getWorkspacePreferences(); @@ -114,44 +121,57 @@ public GeneralTabViewModel(DialogService dialogService, GuiPreferences preferenc this.fileUpdateMonitor = fileUpdateMonitor; this.entryTypesManager = entryTypesManager; - 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")))); - - this.trustStoreManager = new TrustStoreManager(Path.of(preferences.getSSLPreferences().getTruststorePath())); + 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")))); + + this.trustStoreManager = + new TrustStoreManager(Path.of(preferences.getSSLPreferences().getTruststorePath())); } public ValidationStatus remotePortValidationStatus() { @@ -162,13 +182,13 @@ public ValidationStatus remotePortValidationStatus() { 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()); @@ -181,11 +201,13 @@ public void setValues() { openLastStartupProperty.setValue(workspacePreferences.shouldOpenLastEdited()); showAdvancedHintsProperty.setValue(workspacePreferences.shouldShowAdvancedHints()); - inspectionWarningDuplicateProperty.setValue(workspacePreferences.shouldWarnAboutDuplicatesInInspection()); + inspectionWarningDuplicateProperty.setValue( + workspacePreferences.shouldWarnAboutDuplicatesInInspection()); confirmDeleteProperty.setValue(workspacePreferences.shouldConfirmDelete()); - bibliographyModeListProperty.setValue(FXCollections.observableArrayList(BibDatabaseMode.values())); + bibliographyModeListProperty.setValue( + FXCollections.observableArrayList(BibDatabaseMode.values())); selectedBiblatexModeProperty.setValue(libraryPreferences.getDefaultBibDatabaseMode()); alwaysReformatBibProperty.setValue(libraryPreferences.shouldAlwaysReformatOnSave()); @@ -204,25 +226,26 @@ public void storeSettings() { if (newLanguage != workspacePreferences.getLanguage()) { workspacePreferences.setLanguage(newLanguage); Localization.setLanguage(newLanguage); - restartWarning.add(Localization.lang("Changed language") + ": " + newLanguage.getDisplayName()); + restartWarning.add( + Localization.lang("Changed language") + ": " + newLanguage.getDisplayName()); } workspacePreferences.setShouldOverrideDefaultFontSize(fontOverrideProperty.getValue()); workspacePreferences.setMainFontSize(Integer.parseInt(fontSizeProperty.getValue())); switch (selectedThemeProperty.get()) { - case LIGHT -> - workspacePreferences.setTheme(Theme.light()); - case DARK -> - workspacePreferences.setTheme(Theme.dark()); + case LIGHT -> workspacePreferences.setTheme(Theme.light()); + case DARK -> workspacePreferences.setTheme(Theme.dark()); case CUSTOM -> - workspacePreferences.setTheme(Theme.custom(customPathToThemeProperty.getValue())); + workspacePreferences.setTheme( + Theme.custom(customPathToThemeProperty.getValue())); } workspacePreferences.setThemeSyncOs(themeSyncOsProperty.getValue()); workspacePreferences.setOpenLastEdited(openLastStartupProperty.getValue()); workspacePreferences.setShowAdvancedHints(showAdvancedHintsProperty.getValue()); - workspacePreferences.setWarnAboutDuplicatesInInspection(inspectionWarningDuplicateProperty.getValue()); + workspacePreferences.setWarnAboutDuplicatesInInspection( + inspectionWarningDuplicateProperty.getValue()); workspacePreferences.setConfirmDelete(confirmDeleteProperty.getValue()); @@ -232,28 +255,38 @@ public void storeSettings() { libraryPreferences.setAutoSave(autosaveLocalLibraries.getValue()); filePreferences.createBackupProperty().setValue(createBackupProperty.getValue()); - filePreferences.backupDirectoryProperty().setValue(Path.of(backupDirectoryProperty.getValue())); - - getPortAsInt(remotePortProperty.getValue()).ifPresent(newPort -> { - if (remotePreferences.isDifferentPort(newPort)) { - remotePreferences.setPort(newPort); - } - }); - - getPortAsInt(remotePortProperty.getValue()).ifPresent(newPort -> { - if (remotePreferences.isDifferentPort(newPort)) { - remotePreferences.setPort(newPort); - } - }); - - UiMessageHandler uiMessageHandler = Injector.instantiateModelOrService(UiMessageHandler.class); - RemoteListenerServerManager remoteListenerServerManager = Injector.instantiateModelOrService(RemoteListenerServerManager.class); - remoteListenerServerManager.stop(); // stop in all cases, because the port might have changed + filePreferences + .backupDirectoryProperty() + .setValue(Path.of(backupDirectoryProperty.getValue())); + + getPortAsInt(remotePortProperty.getValue()) + .ifPresent( + newPort -> { + if (remotePreferences.isDifferentPort(newPort)) { + remotePreferences.setPort(newPort); + } + }); + + getPortAsInt(remotePortProperty.getValue()) + .ifPresent( + newPort -> { + if (remotePreferences.isDifferentPort(newPort)) { + remotePreferences.setPort(newPort); + } + }); + + UiMessageHandler uiMessageHandler = + Injector.instantiateModelOrService(UiMessageHandler.class); + RemoteListenerServerManager remoteListenerServerManager = + Injector.instantiateModelOrService(RemoteListenerServerManager.class); + remoteListenerServerManager + .stop(); // stop in all cases, because the port might have changed if (remoteServerProperty.getValue()) { remotePreferences.setUseRemoteServer(true); remoteListenerServerManager.openAndStart( - new CLIMessageHandler(uiMessageHandler, preferences, fileUpdateMonitor, entryTypesManager), + new CLIMessageHandler( + uiMessageHandler, preferences, fileUpdateMonitor, entryTypesManager), remotePreferences.getPort()); } else { remotePreferences.setUseRemoteServer(false); @@ -263,7 +296,8 @@ public void storeSettings() { if (remoteServerProperty.getValue()) { remotePreferences.setUseRemoteServer(true); remoteListenerServerManager.openAndStart( - new CLIMessageHandler(uiMessageHandler, preferences, fileUpdateMonitor, entryTypesManager), + new CLIMessageHandler( + uiMessageHandler, preferences, fileUpdateMonitor, entryTypesManager), remotePreferences.getPort()); } else { remotePreferences.setUseRemoteServer(false); @@ -298,8 +332,10 @@ 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; @@ -335,16 +371,27 @@ public StringProperty customPathToThemeProperty() { } public void importCSSFile() { - 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(); - - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(file -> - customPathToThemeProperty.setValue(file.toAbsolutePath().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(); + + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent( + file -> + customPathToThemeProperty.setValue( + file.toAbsolutePath().toString())); } public BooleanProperty fontOverrideProperty() { @@ -397,9 +444,12 @@ public StringProperty backupDirectoryProperty() { public void backupFileDirBrowse() { DirectoryDialogConfiguration dirDialogConfiguration = - new DirectoryDialogConfiguration.Builder().withInitialDirectory(Path.of(backupDirectoryProperty().getValue())).build(); - dialogService.showDirectorySelectionDialog(dirDialogConfiguration) - .ifPresent(dir -> backupDirectoryProperty.setValue(dir.toString())); + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(Path.of(backupDirectoryProperty().getValue())) + .build(); + dialogService + .showDirectorySelectionDialog(dirDialogConfiguration) + .ifPresent(dir -> backupDirectoryProperty.setValue(dir.toString())); } public BooleanProperty remoteServerProperty() { diff --git a/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java b/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java index cf6ada3c627e..e7e2ad9e3410 100644 --- a/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java +++ b/src/main/java/org/jabref/gui/preferences/groups/GroupsTab.java @@ -1,5 +1,7 @@ package org.jabref.gui.preferences.groups; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.RadioButton; @@ -8,9 +10,8 @@ import org.jabref.gui.preferences.PreferencesTab; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - -public class GroupsTab extends AbstractPreferenceTabView implements PreferencesTab { +public class GroupsTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private RadioButton groupViewModeIntersection; @FXML private RadioButton groupViewModeUnion; @@ -18,9 +19,7 @@ public class GroupsTab extends AbstractPreferenceTabView imp @FXML private CheckBox displayGroupCount; public GroupsTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -31,8 +30,12 @@ public String getTabName() { public void initialize() { this.viewModel = new GroupsTabViewModel(preferences.getGroupsPreferences()); - groupViewModeIntersection.selectedProperty().bindBidirectional(viewModel.groupViewModeIntersectionProperty()); - groupViewModeUnion.selectedProperty().bindBidirectional(viewModel.groupViewModeUnionProperty()); + groupViewModeIntersection + .selectedProperty() + .bindBidirectional(viewModel.groupViewModeIntersectionProperty()); + groupViewModeUnion + .selectedProperty() + .bindBidirectional(viewModel.groupViewModeUnionProperty()); autoAssignGroup.selectedProperty().bindBidirectional(viewModel.autoAssignGroupProperty()); displayGroupCount.selectedProperty().bindBidirectional(viewModel.displayGroupCount()); } diff --git a/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java b/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java index 0d1d095108fb..4408dbea44a8 100644 --- a/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/groups/GroupsTabViewModel.java @@ -35,7 +35,8 @@ public void setValues() { @Override public void storeSettings() { - groupsPreferences.setGroupViewMode(GroupViewMode.INTERSECTION, groupViewModeIntersectionProperty.getValue()); + groupsPreferences.setGroupViewMode( + GroupViewMode.INTERSECTION, groupViewModeIntersectionProperty.getValue()); groupsPreferences.setAutoAssignGroup(autoAssignGroupProperty.getValue()); groupsPreferences.setDisplayGroupCount(displayGroupCountProperty.getValue()); } diff --git a/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java b/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java index 1c13439cf6bf..617121f77d6e 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/journals/AbbreviationViewModel.java @@ -1,8 +1,5 @@ package org.jabref.gui.preferences.journals; -import java.util.Locale; -import java.util.Objects; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -10,6 +7,9 @@ import org.jabref.logic.journals.Abbreviation; +import java.util.Locale; +import java.util.Objects; + /** * 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. @@ -30,8 +30,11 @@ public AbbreviationViewModel(Abbreviation abbreviationObject) { this.abbreviation.setValue(abbreviationObject.getAbbreviation()); // the view model stores the "real" values, not the default fallback - String shortestUniqueAbbreviationOfAbbreviation = abbreviationObject.getShortestUniqueAbbreviation(); - boolean shortestUniqueAbbreviationIsDefaultValue = shortestUniqueAbbreviationOfAbbreviation.equals(abbreviationObject.getAbbreviation()); + String shortestUniqueAbbreviationOfAbbreviation = + abbreviationObject.getShortestUniqueAbbreviation(); + boolean shortestUniqueAbbreviationIsDefaultValue = + shortestUniqueAbbreviationOfAbbreviation.equals( + abbreviationObject.getAbbreviation()); if (shortestUniqueAbbreviationIsDefaultValue) { this.shortestUniqueAbbreviation.set(""); } else { @@ -105,13 +108,20 @@ && getShortestUniqueAbbreviation().equals(that.getShortestUniqueAbbreviation()) @Override public int hashCode() { - return Objects.hash(getName(), getAbbreviation(), getShortestUniqueAbbreviation(), isPseudoAbbreviation()); + return Objects.hash( + getName(), + getAbbreviation(), + getShortestUniqueAbbreviation(), + isPseudoAbbreviation()); } 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/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java b/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java index 88ecb96d2409..e78b735b5dfd 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/journals/AbbreviationsFileViewModel.java @@ -1,5 +1,14 @@ package org.jabref.gui.preferences.journals; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; + +import org.jabref.logic.journals.Abbreviation; +import org.jabref.logic.journals.AbbreviationWriter; +import org.jabref.logic.journals.JournalAbbreviationLoader; + import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; @@ -10,23 +19,14 @@ import java.util.Optional; import java.util.stream.Collectors; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleListProperty; -import javafx.collections.FXCollections; - -import org.jabref.logic.journals.Abbreviation; -import org.jabref.logic.journals.AbbreviationWriter; -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. */ public class AbbreviationsFileViewModel { - private final SimpleListProperty abbreviations = new SimpleListProperty<>( - FXCollections.observableArrayList()); + private final SimpleListProperty abbreviations = + new SimpleListProperty<>(FXCollections.observableArrayList()); private final ReadOnlyBooleanProperty isBuiltInList; private final String name; private final Optional path; @@ -51,8 +51,10 @@ public AbbreviationsFileViewModel(List abbreviations, Str public void readAbbreviations() throws IOException { if (path.isPresent()) { - Collection abbreviationList = JournalAbbreviationLoader.readAbbreviationsFromCsvFile(path.get()); - abbreviationList.forEach(abbreviation -> abbreviations.addAll(new AbbreviationViewModel(abbreviation))); + Collection abbreviationList = + JournalAbbreviationLoader.readAbbreviationsFromCsvFile(path.get()); + abbreviationList.forEach( + abbreviation -> abbreviations.addAll(new AbbreviationViewModel(abbreviation))); } else { throw new FileNotFoundException(); } @@ -66,9 +68,10 @@ public void readAbbreviations() throws IOException { public void writeOrCreate() throws IOException { if (!isBuiltInList.get()) { List actualAbbreviations = - abbreviations.stream().filter(abb -> !abb.isPseudoAbbreviation()) - .map(AbbreviationViewModel::getAbbreviationObject) - .collect(Collectors.toList()); + abbreviations.stream() + .filter(abb -> !abb.isPseudoAbbreviation()) + .map(AbbreviationViewModel::getAbbreviationObject) + .collect(Collectors.toList()); AbbreviationWriter.writeOrCreate(path.get(), actualAbbreviations); } } diff --git a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java index 1ca3985fc68b..2e8406ff1c61 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java +++ b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTab.java @@ -1,5 +1,10 @@ package org.jabref.gui.preferences.journals; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; + import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -22,6 +27,7 @@ import javafx.scene.paint.Color; import javafx.util.Duration; +import org.controlsfx.control.textfield.CustomTextField; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.preferences.AbstractPreferenceTabView; import org.jabref.gui.preferences.PreferencesTab; @@ -31,16 +37,13 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.TaskExecutor; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; -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. */ -public class JournalAbbreviationsTab extends AbstractPreferenceTabView implements PreferencesTab { +public class JournalAbbreviationsTab + extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private Label loadingLabel; @FXML private ProgressIndicator progressIndicator; @@ -48,7 +51,10 @@ public class JournalAbbreviationsTab extends AbstractPreferenceTabView journalAbbreviationsTable; @FXML private TableColumn journalTableNameColumn; @FXML private TableColumn journalTableAbbreviationColumn; - @FXML private TableColumn journalTableShortestUniqueAbbreviationColumn; + + @FXML + private TableColumn journalTableShortestUniqueAbbreviationColumn; + @FXML private TableColumn actionsColumn; private FilteredList filteredAbbreviations; @@ -66,18 +72,17 @@ public class JournalAbbreviationsTab extends AbstractPreferenceTabView(viewModel.abbreviationsProperty()); @@ -93,11 +98,14 @@ private void setUpTable() { journalTableNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); journalTableNameColumn.setCellFactory(TextFieldTableCell.forTableColumn()); - journalTableAbbreviationColumn.setCellValueFactory(cellData -> cellData.getValue().abbreviationProperty()); + journalTableAbbreviationColumn.setCellValueFactory( + cellData -> cellData.getValue().abbreviationProperty()); journalTableAbbreviationColumn.setCellFactory(TextFieldTableCell.forTableColumn()); - journalTableShortestUniqueAbbreviationColumn.setCellValueFactory(cellData -> cellData.getValue().shortestUniqueAbbreviationProperty()); - journalTableShortestUniqueAbbreviationColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + journalTableShortestUniqueAbbreviationColumn.setCellValueFactory( + cellData -> cellData.getValue().shortestUniqueAbbreviationProperty()); + journalTableShortestUniqueAbbreviationColumn.setCellFactory( + TextFieldTableCell.forTableColumn()); actionsColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); new ValueTableCellFactory() @@ -105,52 +113,92 @@ private void setUpTable() { .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())) + .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)); - - journalTableNameColumn.editableProperty().bind(viewModel.isAbbreviationEditableAndRemovable()); - journalTableAbbreviationColumn.editableProperty().bind(viewModel.isAbbreviationEditableAndRemovable()); - journalTableShortestUniqueAbbreviationColumn.editableProperty().bind(viewModel.isAbbreviationEditableAndRemovable()); - - removeAbbreviationListButton.disableProperty().bind(viewModel.isFileRemovableProperty().not()); + 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()); + + removeAbbreviationListButton + .disableProperty() + .bind(viewModel.isFileRemovableProperty().not()); journalFilesBox.itemsProperty().bindBidirectional(viewModel.journalFilesProperty()); journalFilesBox.valueProperty().bindBidirectional(viewModel.currentFileProperty()); - addAbbreviationButton.disableProperty().bind(viewModel.isEditableAndRemovableProperty().not()); + addAbbreviationButton + .disableProperty() + .bind(viewModel.isEditableAndRemovableProperty().not()); 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()); } private void setAnimations() { ObjectProperty flashingColor = new SimpleObjectProperty<>(Color.TRANSPARENT); - StringProperty flashingColorStringProperty = createFlashingColorStringProperty(flashingColor); - - 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)) - ); + StringProperty flashingColorStringProperty = + createFlashingColorStringProperty(flashingColor); + + 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))); } @FXML @@ -183,14 +231,18 @@ private void addAbbreviationActions() { editAbbreviation(); } - private static StringProperty createFlashingColorStringProperty(final ObjectProperty flashingColor) { + private static StringProperty createFlashingColorStringProperty( + final ObjectProperty flashingColor) { final StringProperty flashingColorStringProperty = new SimpleStringProperty(); setColorStringFromColor(flashingColorStringProperty, flashingColor); - flashingColor.addListener((observable, oldValue, newValue) -> setColorStringFromColor(flashingColorStringProperty, flashingColor)); + flashingColor.addListener( + (observable, oldValue, newValue) -> + setColorStringFromColor(flashingColorStringProperty, flashingColor)); return flashingColorStringProperty; } - private static void setColorStringFromColor(StringProperty colorStringProperty, ObjectProperty color) { + private static void setColorStringFromColor( + StringProperty colorStringProperty, ObjectProperty color) { colorStringProperty.set(ColorUtil.toRGBACode(color.get())); } diff --git a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java index 106a475df00d..80392d89a1d5 100644 --- a/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/journals/JournalAbbreviationsTabViewModel.java @@ -1,10 +1,6 @@ package org.jabref.gui.preferences.journals; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; +import com.airhacks.afterburner.injection.Injector; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; @@ -24,11 +20,15 @@ import org.jabref.logic.util.BackgroundTask; import org.jabref.logic.util.StandardFileType; import org.jabref.logic.util.TaskExecutor; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + /** * 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. @@ -37,17 +37,22 @@ 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 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 isAbbreviationEditableAndRemovable = + new SimpleBooleanProperty(false); private final SimpleBooleanProperty useFJournal = new SimpleBooleanProperty(true); private final DialogService dialogService; @@ -57,52 +62,63 @@ public class JournalAbbreviationsTabViewModel implements PreferenceTabViewModel private final JournalAbbreviationRepository journalAbbreviationRepository; private boolean shouldWriteLists; - public JournalAbbreviationsTabViewModel(JournalAbbreviationPreferences abbreviationsPreferences, - DialogService dialogService, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository) { + public JournalAbbreviationsTabViewModel( + JournalAbbreviationPreferences abbreviationsPreferences, + DialogService dialogService, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { this.dialogService = Objects.requireNonNull(dialogService); this.taskExecutor = Objects.requireNonNull(taskExecutor); this.journalAbbreviationRepository = Objects.requireNonNull(journalAbbreviationRepository); this.abbreviationsPreferences = abbreviationsPreferences; abbreviationsCount.bind(abbreviations.sizeProperty()); - currentAbbreviation.addListener((observable, oldValue, newValue) -> { - boolean isAbbreviation = (newValue != null) && !newValue.isPseudoAbbreviation(); - boolean isEditableFile = (currentFile.get() != null) && !currentFile.get().isBuiltInListProperty().get(); - isEditableAndRemovable.set(isEditableFile); - isAbbreviationEditableAndRemovable.set(isAbbreviation && isEditableFile); - }); - currentFile.addListener((observable, oldValue, newValue) -> { - if (oldValue != null) { - abbreviations.unbindBidirectional(oldValue.abbreviationsProperty()); - currentAbbreviation.set(null); - } - if (newValue != null) { - isFileRemovable.set(!newValue.isBuiltInListProperty().get()); - abbreviations.bindBidirectional(newValue.abbreviationsProperty()); - if (!abbreviations.isEmpty()) { - currentAbbreviation.set(abbreviations.getLast()); - } - } else { - isFileRemovable.set(false); - if (journalFiles.isEmpty()) { - currentAbbreviation.set(null); - abbreviations.clear(); - } else { - currentFile.set(journalFiles.getFirst()); - } - } - }); - journalFiles.addListener((ListChangeListener) lcl -> { - if (lcl.next()) { - if (!lcl.wasReplaced()) { - if (lcl.wasAdded() && !lcl.getAddedSubList().getFirst().isBuiltInListProperty().get()) { - currentFile.set(lcl.getAddedSubList().getFirst()); + currentAbbreviation.addListener( + (observable, oldValue, newValue) -> { + boolean isAbbreviation = (newValue != null) && !newValue.isPseudoAbbreviation(); + boolean isEditableFile = + (currentFile.get() != null) + && !currentFile.get().isBuiltInListProperty().get(); + isEditableAndRemovable.set(isEditableFile); + isAbbreviationEditableAndRemovable.set(isAbbreviation && isEditableFile); + }); + currentFile.addListener( + (observable, oldValue, newValue) -> { + if (oldValue != null) { + abbreviations.unbindBidirectional(oldValue.abbreviationsProperty()); + currentAbbreviation.set(null); } - } - } - }); + if (newValue != null) { + isFileRemovable.set(!newValue.isBuiltInListProperty().get()); + abbreviations.bindBidirectional(newValue.abbreviationsProperty()); + if (!abbreviations.isEmpty()) { + currentAbbreviation.set(abbreviations.getLast()); + } + } else { + isFileRemovable.set(false); + if (journalFiles.isEmpty()) { + currentAbbreviation.set(null); + abbreviations.clear(); + } else { + currentFile.set(journalFiles.getFirst()); + } + } + }); + journalFiles.addListener( + (ListChangeListener) + lcl -> { + if (lcl.next()) { + if (!lcl.wasReplaced()) { + if (lcl.wasAdded() + && !lcl.getAddedSubList() + .getFirst() + .isBuiltInListProperty() + .get()) { + currentFile.set(lcl.getAddedSubList().getFirst()); + } + } + } + }); } @Override @@ -137,17 +153,21 @@ public void selectLastJournalFile() { * 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) + 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(); - }) + .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); } @@ -157,9 +177,10 @@ public void addBuiltInList() { * basically just calls the {@link #openFile(Path)}} method. */ public void addNewFile() { - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.CSV) - .build(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .addExtensionFilter(StandardFileType.CSV) + .build(); dialogService.showFileSaveDialog(fileDialogConfiguration).ifPresent(this::openFile); } @@ -174,7 +195,8 @@ public void addNewFile() { private void openFile(Path filePath) { AbbreviationsFileViewModel abbreviationsFile = new AbbreviationsFileViewModel(filePath); if (journalFiles.contains(abbreviationsFile)) { - dialogService.showErrorDialogAndWait(Localization.lang("Duplicated Journal File"), + dialogService.showErrorDialogAndWait( + Localization.lang("Duplicated Journal File"), Localization.lang("Journal file %s already added", filePath.toString())); return; } @@ -189,9 +211,10 @@ private void openFile(Path filePath) { } public void openFile() { - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.CSV) - .build(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .addExtensionFilter(StandardFileType.CSV) + .build(); dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(this::openFile); } @@ -218,8 +241,11 @@ public void removeCurrentFile() { 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())); + dialogService.showErrorDialogAndWait( + Localization.lang("Duplicated Journal Abbreviation"), + Localization.lang( + "Abbreviation '%0' for journal '%1' already defined.", + abbreviationObject.getAbbreviation(), abbreviationObject.getName())); } else { abbreviations.add(abbreviationViewModel); currentAbbreviation.set(abbreviationViewModel); @@ -228,10 +254,11 @@ public void addAbbreviation(Abbreviation abbreviationObject) { } public void addAbbreviation() { - addAbbreviation(new Abbreviation( - Localization.lang("Name"), - Localization.lang("Abbreviation"), - Localization.lang("Shortest unique abbreviation"))); + addAbbreviation( + new Abbreviation( + Localization.lang("Name"), + Localization.lang("Abbreviation"), + Localization.lang("Shortest unique abbreviation"))); } /** @@ -244,8 +271,12 @@ void editAbbreviation(Abbreviation abbreviationObject) { if (abbViewModel.equals(currentAbbreviation.get())) { setCurrentAbbreviationNameAndAbbreviationIfValid(abbreviationObject); } else { - dialogService.showErrorDialogAndWait(Localization.lang("Duplicated Journal Abbreviation"), - Localization.lang("Abbreviation '%0' for journal '%1' already defined.", abbreviationObject.getAbbreviation(), abbreviationObject.getName())); + dialogService.showErrorDialogAndWait( + Localization.lang("Duplicated Journal Abbreviation"), + Localization.lang( + "Abbreviation '%0' for journal '%1' already defined.", + abbreviationObject.getAbbreviation(), + abbreviationObject.getName())); } } else { setCurrentAbbreviationNameAndAbbreviationIfValid(abbreviationObject); @@ -268,7 +299,8 @@ private void setCurrentAbbreviationNameAndAbbreviationIfValid(Abbreviation abbre if (abbreviationObject.isDefaultShortestUniqueAbbreviation()) { abbreviationViewModel.setShortestUniqueAbbreviation(""); } else { - abbreviationViewModel.setShortestUniqueAbbreviation(abbreviationObject.getShortestUniqueAbbreviation()); + abbreviationViewModel.setShortestUniqueAbbreviation( + abbreviationObject.getShortestUniqueAbbreviation()); } shouldWriteLists = true; } @@ -279,7 +311,8 @@ private void setCurrentAbbreviationNameAndAbbreviationIfValid(Abbreviation abbre * {@code null} if there are no abbreviations left. */ public void deleteAbbreviation() { - if ((currentAbbreviation.get() != null) && !currentAbbreviation.get().isPseudoAbbreviation()) { + if ((currentAbbreviation.get() != null) + && !currentAbbreviation.get().isPseudoAbbreviation()) { int index = abbreviations.indexOf(currentAbbreviation.get()); if (index > 1) { currentAbbreviation.set(abbreviations.get(index - 1)); @@ -310,13 +343,14 @@ public void removeAbbreviation(AbbreviationViewModel abbreviation) { * Non-existing files will be created. */ public void saveJournalAbbreviationFiles() { - journalFiles.forEach(file -> { - try { - file.writeOrCreate(); - } catch (IOException e) { - LOGGER.debug("Error during writing journal CSV", e); - } - }); + journalFiles.forEach( + file -> { + try { + file.writeOrCreate(); + } catch (IOException e) { + LOGGER.debug("Error during writing journal CSV", e); + } + }); } /** @@ -325,26 +359,37 @@ public void saveJournalAbbreviationFiles() { */ @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)) + 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); } diff --git a/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java b/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java index 5caa9ca82e36..61c71747a148 100644 --- a/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingViewModel.java @@ -1,6 +1,6 @@ package org.jabref.gui.preferences.keybindings; -import java.util.Optional; +import com.google.common.base.CaseFormat; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -15,7 +15,7 @@ import org.jabref.gui.keyboard.KeyBindingCategory; import org.jabref.gui.keyboard.KeyBindingRepository; -import com.google.common.base.CaseFormat; +import java.util.Optional; /** * This class represents a view model for objects of the KeyBinding @@ -28,21 +28,24 @@ public class KeyBindingViewModel { private KeyBinding keyBinding = null; private String realBinding = ""; - private final ObservableList children = FXCollections.observableArrayList(); + 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; - public KeyBindingViewModel(KeyBindingRepository keyBindingRepository, KeyBinding keyBinding, String binding) { + public KeyBindingViewModel( + KeyBindingRepository keyBindingRepository, KeyBinding keyBinding, String binding) { this(keyBindingRepository, keyBinding.getCategory()); this.keyBinding = keyBinding; setDisplayName(); setBinding(binding); } - public KeyBindingViewModel(KeyBindingRepository keyBindingRepository, KeyBindingCategory category) { + public KeyBindingViewModel( + KeyBindingRepository keyBindingRepository, KeyBindingCategory category) { this.keyBindingRepository = keyBindingRepository; this.category = category; setDisplayName(); @@ -75,7 +78,8 @@ private void setBinding(String bind) { } private void setDisplayName() { - this.displayName.set(keyBinding == null ? this.category.getName() : keyBinding.getLocalization()); + this.displayName.set( + keyBinding == null ? this.category.getName() : keyBinding.getLocalization()); } public StringProperty nameProperty() { @@ -97,8 +101,12 @@ public boolean setNewBinding(KeyEvent evt) { // validate the shortcut is no modifier key KeyCode code = evt.getCode(); - if (code.isModifierKey() || (code == KeyCode.BACK_SPACE) || (code == KeyCode.SPACE) || (code == KeyCode.TAB) - || (code == KeyCode.ENTER) || (code == KeyCode.UNDEFINED)) { + if (code.isModifierKey() + || (code == KeyCode.BACK_SPACE) + || (code == KeyCode.SPACE) + || (code == KeyCode.TAB) + || (code == KeyCode.ENTER) + || (code == KeyCode.UNDEFINED)) { return false; } diff --git a/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java b/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java index 20bd57283255..897d0955ee91 100644 --- a/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java +++ b/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTab.java @@ -1,5 +1,10 @@ package org.jabref.gui.preferences.keybindings; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; + import javafx.fxml.FXML; import javafx.scene.control.MenuButton; import javafx.scene.control.MenuItem; @@ -18,11 +23,8 @@ import org.jabref.gui.util.ViewModelTreeTableCellFactory; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; - -public class KeyBindingsTab extends AbstractPreferenceTabView implements PreferencesTab { +public class KeyBindingsTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TreeTableView keyBindingsTable; @FXML private TreeTableColumn actionColumn; @@ -34,9 +36,7 @@ public class KeyBindingsTab extends AbstractPreferenceTabView 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()); + shortcutColumn.setCellValueFactory( + cellData -> cellData.getValue().getValue().shownBindingProperty()); new ViewModelTreeTableCellFactory() - .withGraphic(keyBinding -> keyBinding.getResetIcon().map(JabRefIcon::getGraphicNode).orElse(null)) + .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)) + .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))); + viewModel + .keyBindingPresets() + .forEach(preset -> presetsButton.getItems().add(createMenuItem(preset))); } private MenuItem createMenuItem(KeyBindingPreset preset) { diff --git a/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java b/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java index 0af50012a236..b8375ae2fc52 100644 --- a/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/keybindings/KeyBindingsTabViewModel.java @@ -1,10 +1,5 @@ package org.jabref.gui.preferences.keybindings; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleListProperty; @@ -26,19 +21,29 @@ import org.jabref.gui.util.OptionalObjectProperty; import org.jabref.logic.l10n.Localization; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + public class KeyBindingsTabViewModel implements PreferenceTabViewModel { private final KeyBindingRepository keyBindingRepository; private final GuiPreferences preferences; - private final OptionalObjectProperty selectedKeyBinding = OptionalObjectProperty.empty(); + 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); @@ -52,15 +57,22 @@ public KeyBindingsTabViewModel(KeyBindingRepository keyBindingRepository, Dialog */ @Override public void setValues() { - KeyBindingViewModel root = new KeyBindingViewModel(keyBindingRepository, KeyBindingCategory.FILE); + KeyBindingViewModel root = + new KeyBindingViewModel(keyBindingRepository, KeyBindingCategory.FILE); for (KeyBindingCategory category : KeyBindingCategory.values()) { - KeyBindingViewModel categoryItem = new KeyBindingViewModel(keyBindingRepository, category); - keyBindingRepository.getKeyBindings().forEach((keyBinding, bind) -> { - if (keyBinding.getCategory() == category) { - KeyBindingViewModel keyBindViewModel = new KeyBindingViewModel(keyBindingRepository, keyBinding, bind); - categoryItem.getChildren().add(keyBindViewModel); - } - }); + KeyBindingViewModel categoryItem = + new KeyBindingViewModel(keyBindingRepository, category); + keyBindingRepository + .getKeyBindings() + .forEach( + (keyBinding, bind) -> { + if (keyBinding.getCategory() == category) { + KeyBindingViewModel keyBindViewModel = + new KeyBindingViewModel( + keyBindingRepository, keyBinding, bind); + categoryItem.getChildren().add(keyBindViewModel); + } + }); root.getChildren().add(categoryItem); } rootKeyBinding.set(root); @@ -84,22 +96,33 @@ public void setNewBindingForCurrent(KeyEvent event) { public void storeSettings() { if (!keyBindingRepository.equals(preferences.getKeyBindingRepository())) { - preferences.getKeyBindingRepository().getBindingsProperty().set(keyBindingRepository.getBindingsProperty()); + preferences + .getKeyBindingRepository() + .getBindingsProperty() + .set(keyBindingRepository.getBindingsProperty()); restartWarning.add(Localization.lang("Keyboard shortcuts changed")); } } public void resetToDefault() { String title = Localization.lang("Resetting all keyboard shortcuts"); - String content = Localization.lang("All keyboard shortcuts will be reset to their defaults."); + 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) { diff --git a/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java b/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java index 72265447c39d..a0a2c7c7fbdf 100644 --- a/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java +++ b/src/main/java/org/jabref/gui/preferences/keybindings/presets/BashKeyBindingPreset.java @@ -1,10 +1,10 @@ package org.jabref.gui.preferences.keybindings.presets; +import org.jabref.gui.keyboard.KeyBinding; + import java.util.HashMap; import java.util.Map; -import org.jabref.gui.keyboard.KeyBinding; - public class BashKeyBindingPreset implements KeyBindingPreset { @Override diff --git a/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java b/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java index 4eae173e8de9..562deeeab245 100644 --- a/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java +++ b/src/main/java/org/jabref/gui/preferences/keybindings/presets/KeyBindingPreset.java @@ -1,9 +1,9 @@ package org.jabref.gui.preferences.keybindings.presets; -import java.util.Map; - import org.jabref.gui.keyboard.KeyBinding; +import java.util.Map; + public interface KeyBindingPreset { String getName(); diff --git a/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java b/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java index dc4993e67abb..abb01f0d9083 100644 --- a/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java +++ b/src/main/java/org/jabref/gui/preferences/keybindings/presets/NewEntryBindingPreset.java @@ -1,11 +1,11 @@ package org.jabref.gui.preferences.keybindings.presets; -import java.util.HashMap; -import java.util.Map; - import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.l10n.Localization; +import java.util.HashMap; +import java.util.Map; + public class NewEntryBindingPreset implements KeyBindingPreset { @Override diff --git a/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java b/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java index c624862c6843..ed2e4e4f5f05 100644 --- a/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java +++ b/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java @@ -1,5 +1,9 @@ package org.jabref.gui.preferences.linkedfiles; +import com.airhacks.afterburner.views.ViewLoader; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -18,10 +22,8 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - -public class LinkedFilesTab extends AbstractPreferenceTabView implements PreferencesTab { +public class LinkedFilesTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TextField mainFileDirectory; @FXML private RadioButton useMainFileDirectory; @@ -43,9 +45,7 @@ public class LinkedFilesTab extends AbstractPreferenceTabView validationVisualizer.initVisualization(viewModel.mainFileDirValidationStatus(), mainFileDirectory)); + Platform.runLater( + () -> + validationVisualizer.initVisualization( + viewModel.mainFileDirValidationStatus(), mainFileDirectory)); } public void mainFileDirBrowse() { diff --git a/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java b/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java index 437d6d713fb3..4a6932af069f 100644 --- a/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java @@ -1,8 +1,9 @@ package org.jabref.gui.preferences.linkedfiles; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -20,10 +21,9 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.io.AutoLinkPreferences; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; public class LinkedFilesTabViewModel implements PreferenceTabViewModel { @@ -35,7 +35,8 @@ public class LinkedFilesTabViewModel implements PreferenceTabViewModel { private final BooleanProperty autolinkUseRegexProperty = new SimpleBooleanProperty(); private final StringProperty autolinkRegexKeyProperty = new SimpleStringProperty(""); private final ListProperty defaultFileNamePatternsProperty = - new SimpleListProperty<>(FXCollections.observableArrayList(FilePreferences.DEFAULT_FILENAME_PATTERNS)); + 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(); @@ -53,32 +54,36 @@ 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 public void setValues() { // External files preferences / Attached files preferences / File preferences - mainFileDirectoryProperty.setValue(filePreferences.getMainFileDirectory().orElse(Path.of("")).toString()); + mainFileDirectoryProperty.setValue( + filePreferences.getMainFileDirectory().orElse(Path.of("")).toString()); useMainFileDirectoryProperty.setValue(!filePreferences.shouldStoreFilesRelativeToBibFile()); - useBibLocationAsPrimaryProperty.setValue(filePreferences.shouldStoreFilesRelativeToBibFile()); + useBibLocationAsPrimaryProperty.setValue( + filePreferences.shouldStoreFilesRelativeToBibFile()); fulltextIndex.setValue(filePreferences.shouldFulltextIndexLinkedFiles()); fileNamePatternProperty.setValue(filePreferences.getFileNamePattern()); fileDirectoryPatternProperty.setValue(filePreferences.getFileDirectoryPattern()); @@ -106,11 +111,14 @@ public void storeSettings() { // Autolink preferences if (autolinkFileStartsBibtexProperty.getValue()) { - autoLinkPreferences.setCitationKeyDependency(AutoLinkPreferences.CitationKeyDependency.START); + autoLinkPreferences.setCitationKeyDependency( + AutoLinkPreferences.CitationKeyDependency.START); } else if (autolinkFileExactBibtexProperty.getValue()) { - autoLinkPreferences.setCitationKeyDependency(AutoLinkPreferences.CitationKeyDependency.EXACT); + autoLinkPreferences.setCitationKeyDependency( + AutoLinkPreferences.CitationKeyDependency.EXACT); } else if (autolinkUseRegexProperty.getValue()) { - autoLinkPreferences.setCitationKeyDependency(AutoLinkPreferences.CitationKeyDependency.REGEX); + autoLinkPreferences.setCitationKeyDependency( + AutoLinkPreferences.CitationKeyDependency.REGEX); } autoLinkPreferences.setRegularExpression(autolinkRegexKeyProperty.getValue()); @@ -126,8 +134,10 @@ 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; @@ -135,9 +145,12 @@ public boolean validateSettings() { public void mainFileDirBrowse() { DirectoryDialogConfiguration dirDialogConfiguration = - new DirectoryDialogConfiguration.Builder().withInitialDirectory(Path.of(mainFileDirectoryProperty.getValue())).build(); - dialogService.showDirectorySelectionDialog(dirDialogConfiguration) - .ifPresent(f -> mainFileDirectoryProperty.setValue(f.toString())); + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(Path.of(mainFileDirectoryProperty.getValue())) + .build(); + dialogService + .showDirectorySelectionDialog(dirDialogConfiguration) + .ifPresent(f -> mainFileDirectoryProperty.setValue(f.toString())); } // External file links @@ -193,4 +206,3 @@ public BooleanProperty moveToTrashProperty() { return this.moveToTrashProperty; } } - diff --git a/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java b/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java index 50582e5a8596..388a9391df31 100644 --- a/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java +++ b/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTab.java @@ -1,5 +1,7 @@ package org.jabref.gui.preferences.nameformatter; +import com.airhacks.afterburner.views.ViewLoader; + import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.TableColumn; @@ -19,9 +21,8 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; - -public class NameFormatterTab extends AbstractPreferenceTabView implements PreferencesTab { +public class NameFormatterTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TableView formatterList; @FXML private TableColumn formatterNameColumn; @@ -32,9 +33,7 @@ public class NameFormatterTab extends AbstractPreferenceTabView() .withGraphic(name -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) .withTooltip(name -> Localization.lang("Remove formatter '%0'", name)) - .withOnMouseClickedEvent(item -> evt -> - viewModel.removeFormatter(formatterList.getFocusModel().getFocusedItem())) + .withOnMouseClickedEvent( + item -> + evt -> + viewModel.removeFormatter( + formatterList.getFocusModel().getFocusedItem())) .install(actionsColumn); - formatterList.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.DELETE) { - viewModel.removeFormatter(formatterList.getSelectionModel().getSelectedItem()); - event.consume(); - } - }); + formatterList.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.DELETE) { + viewModel.removeFormatter( + formatterList.getSelectionModel().getSelectedItem()); + event.consume(); + } + }); formatterList.setEditable(true); formatterList.itemsProperty().bindBidirectional(viewModel.formatterListProperty()); addFormatterName.textProperty().bindBidirectional(viewModel.addFormatterNameProperty()); - addFormatterName.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.ENTER) { - addFormatterString.requestFocus(); - addFormatterString.selectAll(); - event.consume(); - } - }); + addFormatterName.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.ENTER) { + addFormatterString.requestFocus(); + addFormatterString.selectAll(); + event.consume(); + } + }); addFormatterString.textProperty().bindBidirectional(viewModel.addFormatterStringProperty()); - addFormatterString.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.ENTER) { - viewModel.addFormatter(); - addFormatterName.requestFocus(); - event.consume(); - } - }); + addFormatterString.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.ENTER) { + viewModel.addFormatter(); + addFormatterName.requestFocus(); + event.consume(); + } + }); 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() { diff --git a/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java b/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java index 4efae9eea8e4..90dd23ef88ed 100644 --- a/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/nameformatter/NameFormatterTabViewModel.java @@ -1,8 +1,5 @@ package org.jabref.gui.preferences.nameformatter; -import java.util.ArrayList; -import java.util.List; - import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleStringProperty; @@ -13,9 +10,13 @@ import org.jabref.logic.layout.format.NameFormatterPreferences; import org.jabref.model.strings.StringUtil; +import java.util.ArrayList; +import java.util.List; + 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(); @@ -56,10 +57,12 @@ 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(""); diff --git a/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java b/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java index 48844b9056f8..0245b10999ad 100644 --- a/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/network/CustomCertificateViewModel.java @@ -1,8 +1,5 @@ package org.jabref.gui.preferences.network; -import java.time.LocalDate; -import java.util.Optional; - import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; @@ -13,6 +10,9 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.logic.net.ssl.SSLCertificate; +import java.time.LocalDate; +import java.util.Optional; + public class CustomCertificateViewModel extends AbstractViewModel { private final StringProperty serialNumberProperty = new SimpleStringProperty(""); private final StringProperty issuerProperty = new SimpleStringProperty(""); @@ -23,7 +23,14 @@ public class CustomCertificateViewModel extends AbstractViewModel { 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); diff --git a/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java b/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java index 232324c4095d..79a6ff30edaf 100644 --- a/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java +++ b/src/main/java/org/jabref/gui/preferences/network/NetworkTab.java @@ -1,7 +1,9 @@ package org.jabref.gui.preferences.network; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; import javafx.application.Platform; import javafx.beans.binding.BooleanBinding; @@ -17,6 +19,7 @@ import javafx.scene.control.Tooltip; import javafx.scene.input.MouseEvent; +import org.controlsfx.control.textfield.CustomPasswordField; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.preferences.AbstractPreferenceTabView; import org.jabref.gui.preferences.PreferencesTab; @@ -24,12 +27,11 @@ import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import org.controlsfx.control.textfield.CustomPasswordField; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; -public class NetworkTab extends AbstractPreferenceTabView implements PreferencesTab { +public class NetworkTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private CheckBox versionCheck; @FXML private CheckBox proxyUse; @FXML private Label proxyHostnameLabel; @@ -43,7 +45,10 @@ public class NetworkTab extends AbstractPreferenceTabView i @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 SplitPane + persistentTooltipWrapper; // The disabled persistPassword control does not show tooltips @FXML private TableView customCertificatesTable; @FXML private TableColumn certIssuer; @@ -60,9 +65,7 @@ public class NetworkTab extends AbstractPreferenceTabView i private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public NetworkTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -82,55 +85,84 @@ public void initialize() { proxyPortLabel.disableProperty().bind(proxyUse.selectedProperty().not()); proxyPort.textProperty().bindBidirectional(viewModel.proxyPortProperty()); proxyPort.disableProperty().bind(proxyUse.selectedProperty().not()); - proxyUseAuthentication.selectedProperty().bindBidirectional(viewModel.proxyUseAuthenticationProperty()); + 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()); proxyPasswordLabel.disableProperty().bind(proxyCustomAndAuthentication.not()); proxyPassword.textProperty().bindBidirectional(viewModel.proxyPasswordProperty()); proxyPassword.disableProperty().bind(proxyCustomAndAuthentication.not()); - proxyPersistPassword.selectedProperty().bindBidirectional(viewModel.proxyPersistPasswordProperty()); - 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 { - persistentTooltipWrapper.setTooltip(null); - } - }); + proxyPersistPassword + .selectedProperty() + .bindBidirectional(viewModel.proxyPersistPasswordProperty()); + 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 { + persistentTooltipWrapper.setTooltip(null); + } + }); proxyPassword.setRight(IconTheme.JabRefIcons.PASSWORD_REVEALED.getGraphicNode()); - proxyPassword.getRight().addEventFilter(MouseEvent.MOUSE_PRESSED, this::proxyPasswordReveal); + proxyPassword + .getRight() + .addEventFilter(MouseEvent.MOUSE_PRESSED, this::proxyPasswordReveal); proxyPassword.getRight().addEventFilter(MouseEvent.MOUSE_RELEASED, this::proxyPasswordMask); proxyPassword.getRight().addEventFilter(MouseEvent.MOUSE_EXITED, this::proxyPasswordMask); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> { - validationVisualizer.initVisualization(viewModel.proxyHostnameValidationStatus(), proxyHostname); - validationVisualizer.initVisualization(viewModel.proxyPortValidationStatus(), proxyPort); - validationVisualizer.initVisualization(viewModel.proxyUsernameValidationStatus(), proxyUsername); - validationVisualizer.initVisualization(viewModel.proxyPasswordValidationStatus(), proxyPassword); - }); + Platform.runLater( + () -> { + validationVisualizer.initVisualization( + viewModel.proxyHostnameValidationStatus(), proxyHostname); + validationVisualizer.initVisualization( + viewModel.proxyPortValidationStatus(), proxyPort); + validationVisualizer.initVisualization( + viewModel.proxyUsernameValidationStatus(), proxyUsername); + validationVisualizer.initVisualization( + viewModel.proxyPasswordValidationStatus(), proxyPassword); + }); certSerialNumber.setCellValueFactory(data -> data.getValue().serialNumberProperty()); certIssuer.setCellValueFactory(data -> data.getValue().issuerProperty()); - certSignatureAlgorithm.setCellValueFactory(data -> data.getValue().signatureAlgorithmProperty()); - certVersion.setCellValueFactory(data -> EasyBind.map(data.getValue().versionProperty(), this::formatVersion)); + certSignatureAlgorithm.setCellValueFactory( + data -> data.getValue().signatureAlgorithmProperty()); + certVersion.setCellValueFactory( + data -> EasyBind.map(data.getValue().versionProperty(), this::formatVersion)); - certValidFrom.setCellValueFactory(data -> EasyBind.map(data.getValue().validFromProperty(), this::formatDate)); - certValidTo.setCellValueFactory(data -> EasyBind.map(data.getValue().validToProperty(), this::formatDate)); + certValidFrom.setCellValueFactory( + data -> EasyBind.map(data.getValue().validFromProperty(), this::formatDate)); + certValidTo.setCellValueFactory( + data -> EasyBind.map(data.getValue().validToProperty(), this::formatDate)); customCertificatesTable.itemsProperty().set(viewModel.customCertificateListProperty()); - actionsColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getThumbprint())); + 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))) + .withOnMouseClickedEvent( + thumbprint -> + evt -> + viewModel + .customCertificateListProperty() + .removeIf( + cert -> + cert.getThumbprint() + .equals(thumbprint))) .install(actionsColumn); } diff --git a/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java b/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java index 9035c8394ed1..16e021f41df1 100644 --- a/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/network/NetworkTabViewModel.java @@ -1,11 +1,10 @@ package org.jabref.gui.preferences.network; -import java.net.MalformedURLException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -18,6 +17,8 @@ import javafx.collections.ListChangeListener; import javafx.stage.FileChooser; +import kong.unirest.core.UnirestException; + import org.jabref.gui.DialogService; import org.jabref.gui.preferences.PreferenceTabViewModel; import org.jabref.gui.util.FileDialogConfiguration; @@ -33,12 +34,12 @@ import org.jabref.logic.util.StandardFileType; import org.jabref.model.strings.StringUtil; -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; -import kong.unirest.core.UnirestException; +import java.net.MalformedURLException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; public class NetworkTabViewModel implements PreferenceTabViewModel { private final BooleanProperty versionCheckProperty = new SimpleBooleanProperty(); @@ -50,7 +51,8 @@ public class NetworkTabViewModel implements PreferenceTabViewModel { 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; @@ -60,7 +62,6 @@ public class NetworkTabViewModel implements PreferenceTabViewModel { private final DialogService dialogService; private final CliPreferences preferences; - private final ProxyPreferences proxyPreferences; private final ProxyPreferences backupProxyPreferences; private final InternalPreferences internalPreferences; @@ -69,55 +70,68 @@ public class NetworkTabViewModel implements PreferenceTabViewModel { 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, - 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, - 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")))); - - this.trustStoreManager = new TrustStoreManager(Path.of(preferences.getSSLPreferences().getTruststorePath())); + 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, + 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")))); + + this.trustStoreManager = + new TrustStoreManager(Path.of(preferences.getSSLPreferences().getTruststorePath())); } @Override @@ -141,20 +155,37 @@ private void setProxyValues() { private void setSSLValues() { customCertificateListProperty.clear(); - 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 - .addCertificate(formatCustomAlias(certificate.getThumbprint()), Path.of(path))); - } else if (c.wasRemoved()) { - CustomCertificateViewModel certificate = c.getRemoved().getFirst(); - trustStoreManager.deleteCertificate(formatCustomAlias(certificate.getThumbprint())); - } - } - }); + 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.addCertificate( + formatCustomAlias( + certificate + .getThumbprint()), + Path.of(path))); + } else if (c.wasRemoved()) { + CustomCertificateViewModel certificate = + c.getRemoved().getFirst(); + trustStoreManager.deleteCertificate( + formatCustomAlias(certificate.getThumbprint())); + } + } + }); } @Override @@ -165,7 +196,9 @@ 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); @@ -212,8 +245,10 @@ 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; @@ -229,15 +264,15 @@ 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 { @@ -306,20 +341,45 @@ 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")); - } - })); + 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")); + } + })); } private String formatCustomAlias(String thumbprint) { diff --git a/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java b/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java index baa36aa4b112..01463b156fab 100644 --- a/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java +++ b/src/main/java/org/jabref/gui/preferences/preview/PreviewTab.java @@ -1,7 +1,11 @@ package org.jabref.gui.preferences.preview; -import java.util.ArrayList; -import java.util.List; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.beans.property.ListProperty; @@ -20,6 +24,9 @@ import javafx.scene.input.MouseEvent; import javafx.scene.input.TransferMode; +import org.controlsfx.control.textfield.CustomTextField; +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.LineNumberFactory; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.SimpleCommand; @@ -39,15 +46,11 @@ import org.jabref.logic.util.TestEntry; import org.jabref.model.database.BibDatabaseContext; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; -import org.controlsfx.control.textfield.CustomTextField; -import org.fxmisc.richtext.CodeArea; -import org.fxmisc.richtext.LineNumberFactory; +import java.util.ArrayList; +import java.util.List; -public class PreviewTab extends AbstractPreferenceTabView implements PreferencesTab { +public class PreviewTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private CheckBox showAsTabCheckBox; @FXML private CheckBox showPreviewTooltipCheckBox; @@ -74,9 +77,7 @@ public class PreviewTab extends AbstractPreferenceTabView i 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,14 +92,10 @@ 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(); } @@ -112,34 +109,52 @@ 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(); - - dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(bstFile -> { - viewModel.addBstStyle(bstFile); - }); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .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()); - showPreviewTooltipCheckBox.selectedProperty().bindBidirectional(viewModel.showPreviewInEntryTableTooltip()); + showPreviewTooltipCheckBox + .selectedProperty() + .bindBidirectional(viewModel.showPreviewInEntryTableTooltip()); searchBox.setPromptText(Localization.lang("Search") + "..."); 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"); @@ -150,12 +165,17 @@ public void initialize() { .install(availableListView); availableListView.setOnDragOver(this::dragOver); availableListView.setOnDragDetected(this::dragDetectedInAvailable); - availableListView.setOnDragDropped(event -> dragDropped(viewModel.availableListProperty(), event)); + 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()); @@ -165,30 +185,92 @@ public void initialize() { .install(chosenListView); chosenListView.setOnDragOver(this::dragOver); chosenListView.setOnDragDetected(this::dragDetectedInChosen); - chosenListView.setOnDragDropped(event -> dragDropped(viewModel.chosenListProperty(), event)); + 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()); - - PreviewViewer previewViewer = new PreviewViewer(new BibDatabaseContext(), dialogService, preferences, themeManager, taskExecutor); + 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( + new BibDatabaseContext(), + dialogService, + preferences, + themeManager, + taskExecutor); 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())); + editArea.visibleProperty() + .bind( + viewModel + .chosenSelectionModelProperty() + .getValue() + .selectedItemProperty() + .isNotNull() + .or( + viewModel + .availableSelectionModelProperty() + .getValue() + .selectedItemProperty() + .isNotNull())); BindingsHelper.bindBidirectional( editArea.textProperty(), @@ -199,25 +281,44 @@ public void initialize() { viewModel.refreshPreview(); }); - editArea.textProperty().addListener((obs, oldValue, newValue) -> - editArea.setStyleSpans(0, viewModel.computeHighlighting(newValue))); - - editArea.focusedProperty().addListener((observable, oldValue, newValue) -> { - if (!newValue) { - viewModel.refreshPreview(); - } - }); - - searchBox.textProperty().addListener((observable, previousText, searchTerm) -> viewModel.setAvailableFilter(searchTerm)); + editArea.textProperty() + .addListener( + (obs, oldValue, newValue) -> + editArea.setStyleSpans(0, viewModel.computeHighlighting(newValue))); + + editArea.focusedProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (!newValue) { + viewModel.refreshPreview(); + } + }); + + searchBox + .textProperty() + .addListener( + (observable, previousText, searchTerm) -> + viewModel.setAvailableFilter(searchTerm)); readOnlyLabel.visibleProperty().bind(viewModel.selectedIsEditableProperty().not()); resetDefaultButton.disableProperty().bind(viewModel.selectedIsEditableProperty().not()); - contextMenu.getItems().getFirst().disableProperty().bind(viewModel.selectedIsEditableProperty().not()); - contextMenu.getItems().get(2).disableProperty().bind(viewModel.selectedIsEditableProperty().not()); + contextMenu + .getItems() + .getFirst() + .disableProperty() + .bind(viewModel.selectedIsEditableProperty().not()); + contextMenu + .getItems() + .get(2) + .disableProperty() + .bind(viewModel.selectedIsEditableProperty().not()); editArea.editableProperty().bind(viewModel.selectedIsEditableProperty()); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.chosenListValidationStatus(), chosenListView)); + Platform.runLater( + () -> + validationVisualizer.initVisualization( + viewModel.chosenListValidationStatus(), chosenListView)); } /** @@ -227,7 +328,6 @@ public void initialize() { * @param list The ListView currently focused * @param keypressed The pressed character */ - private void jumpToSearchKey(ListView list, KeyEvent keypressed) { if (keypressed.getCharacter() == null) { return; @@ -241,8 +341,10 @@ private void jumpToSearchKey(ListView list, KeyEvent keypressed) 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) { @@ -250,19 +352,31 @@ 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(); } diff --git a/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java b/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java index dcef02aaa9f0..188a0fc43fa1 100644 --- a/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/preview/PreviewTabViewModel.java @@ -1,13 +1,11 @@ package org.jabref.gui.preferences.preview; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import com.airhacks.afterburner.injection.Injector; + +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -25,6 +23,8 @@ import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; import org.jabref.gui.DialogService; import org.jabref.gui.DragAndDropDataFormats; import org.jabref.gui.StateManager; @@ -41,17 +41,18 @@ import org.jabref.logic.util.BackgroundTask; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.BibEntryTypesManager; - -import com.airhacks.afterburner.injection.Injector; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; -import org.fxmisc.richtext.model.StyleSpans; -import org.fxmisc.richtext.model.StyleSpansBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * This class is Preferences -> Entry Preview tab model *

    @@ -67,16 +68,23 @@ public class PreviewTabViewModel implements PreferenceTabViewModel { 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 ListProperty bstStylesPaths = + new SimpleListProperty<>(FXCollections.observableArrayList()); private final BooleanProperty selectedIsEditableProperty = new SimpleBooleanProperty(false); - private final ObjectProperty selectedLayoutProperty = new SimpleObjectProperty<>(); + private final ObjectProperty selectedLayoutProperty = + new SimpleObjectProperty<>(); private final StringProperty sourceTextProperty = new SimpleStringProperty(""); private final DialogService dialogService; @@ -89,31 +97,35 @@ public class PreviewTabViewModel implements PreferenceTabViewModel { 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(); this.previewPreferences = previewPreferences; - sourceTextProperty.addListener((observable, oldValue, newValue) -> { - if (selectedLayoutProperty.getValue() instanceof TextBasedPreviewLayout layout) { - layout.setText(sourceTextProperty.getValue()); - } - }); - - 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") - ) - ) - ); + sourceTextProperty.addListener( + (observable, oldValue, newValue) -> { + if (selectedLayoutProperty.getValue() + instanceof TextBasedPreviewLayout layout) { + layout.setText(sourceTextProperty.getValue()); + } + }); + + 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")))); } @Override @@ -128,26 +140,47 @@ public void setValues() { availableListProperty.getValue().add(previewPreferences.getCustomPreviewLayout()); } - BibEntryTypesManager entryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); + BibEntryTypesManager entryTypesManager = + Injector.instantiateModelOrService(BibEntryTypesManager.class); BackgroundTask.wrap(CitationStyle::discoverCitationStyles) - .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 -> { - BstPreviewLayout layout = new BstPreviewLayout(path); - availableListProperty.add(layout); - }); + bstStylesPaths.forEach( + path -> { + BstPreviewLayout layout = new BstPreviewLayout(path); + availableListProperty.add(layout); + }); } public void setPreviewLayout(PreviewLayout selectedLayout) { @@ -163,7 +196,10 @@ public void setPreviewLayout(PreviewLayout selectedLayout) { LOGGER.warn("Parsing error.", exception); dialogService.showErrorDialogAndWait( Localization.lang("Parsing error"), - Localization.lang("Parsing error") + ": " + Localization.lang("illegal backslash expression"), exception); + Localization.lang("Parsing error") + + ": " + + Localization.lang("illegal backslash expression"), + exception); } boolean isEditingAllowed = selectedLayout instanceof TextBasedPreviewLayout; @@ -181,11 +217,14 @@ 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)); } /** @@ -205,13 +244,20 @@ public void storeSettings() { previewPreferences.getLayoutCycle().clear(); previewPreferences.getLayoutCycle().addAll(chosenListProperty); previewPreferences.setShowPreviewAsExtraTab(showAsExtraTabProperty.getValue()); - previewPreferences.setShowPreviewEntryTableTooltip(showPreviewInEntryTableTooltip.getValue()); + previewPreferences.setShowPreviewEntryTableTooltip( + showPreviewInEntryTableTooltip.getValue()); previewPreferences.setCustomPreviewLayout((TextBasedPreviewLayout) customLayout); 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 +270,11 @@ 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; } @@ -233,18 +282,21 @@ public boolean validateSettings() { } public void addToChosen() { - List selected = new ArrayList<>(availableSelectionModelProperty.getValue().getSelectedItems()); + List selected = + new ArrayList<>(availableSelectionModelProperty.getValue().getSelectedItems()); availableSelectionModelProperty.getValue().clearSelection(); availableListProperty.removeAll(selected); chosenListProperty.addAll(selected); } public void removeFromChosen() { - List selected = new ArrayList<>(chosenSelectionModelProperty.getValue().getSelectedItems()); + List selected = + new ArrayList<>(chosenSelectionModelProperty.getValue().getSelectedItems()); chosenSelectionModelProperty.getValue().clearSelection(); chosenListProperty.removeAll(selected); availableListProperty.addAll(selected); - availableListProperty.sort((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())); + availableListProperty.sort( + (a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())); } public void selectedInChosenUp() { @@ -252,7 +304,8 @@ public void selectedInChosenUp() { return; } - List selected = new ArrayList<>(chosenSelectionModelProperty.getValue().getSelectedIndices()); + List selected = + new ArrayList<>(chosenSelectionModelProperty.getValue().getSelectedIndices()); List newIndices = new ArrayList<>(); chosenSelectionModelProperty.getValue().clearSelection(); @@ -273,14 +326,18 @@ public void selectedInChosenDown() { return; } - List selected = new ArrayList<>(chosenSelectionModelProperty.getValue().getSelectedIndices()); + List selected = + new ArrayList<>(chosenSelectionModelProperty.getValue().getSelectedIndices()); List newIndices = new ArrayList<>(); chosenSelectionModelProperty.getValue().clearSelection(); for (int i = selected.size() - 1; i >= 0; i--) { int oldIndex = selected.get(i); boolean alreadyTaken = newIndices.contains(oldIndex + 1); - int newIndex = (oldIndex < (chosenListProperty.size() - 1)) && !alreadyTaken ? oldIndex + 1 : oldIndex; + int newIndex = + (oldIndex < (chosenListProperty.size() - 1)) && !alreadyTaken + ? oldIndex + 1 + : oldIndex; chosenListProperty.add(newIndex, chosenListProperty.remove(oldIndex)); newIndices.add(newIndex); } @@ -311,8 +368,10 @@ public void resetDefaultLayout() { * @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; @@ -334,28 +393,47 @@ public StyleSpans> computeHighlighting(String text) { if (matcher.group("ELEMENT") != null) { String attributesText = matcher.group(GROUP_ATTRIBUTES_SECTION); - spansBuilder.add(Collections.singleton("tagmark"), matcher.end(GROUP_OPEN_BRACKET) - matcher.start(GROUP_OPEN_BRACKET)); - spansBuilder.add(Collections.singleton("anytag"), matcher.end(GROUP_ELEMENT_NAME) - matcher.end(GROUP_OPEN_BRACKET)); + spansBuilder.add( + Collections.singleton("tagmark"), + matcher.end(GROUP_OPEN_BRACKET) - matcher.start(GROUP_OPEN_BRACKET)); + spansBuilder.add( + Collections.singleton("anytag"), + matcher.end(GROUP_ELEMENT_NAME) - matcher.end(GROUP_OPEN_BRACKET)); if (!attributesText.isEmpty()) { lastKeywordEnd = 0; Matcher attributesMatcher = ATTRIBUTES.matcher(attributesText); while (attributesMatcher.find()) { - spansBuilder.add(Collections.emptyList(), attributesMatcher.start() - lastKeywordEnd); - spansBuilder.add(Collections.singleton("attribute"), attributesMatcher.end(GROUP_ATTRIBUTE_NAME) - attributesMatcher.start(GROUP_ATTRIBUTE_NAME)); - spansBuilder.add(Collections.singleton("tagmark"), attributesMatcher.end(GROUP_EQUAL_SYMBOL) - attributesMatcher.end(GROUP_ATTRIBUTE_NAME)); - spansBuilder.add(Collections.singleton("avalue"), attributesMatcher.end(GROUP_ATTRIBUTE_VALUE) - attributesMatcher.end(GROUP_EQUAL_SYMBOL)); + spansBuilder.add( + Collections.emptyList(), + attributesMatcher.start() - lastKeywordEnd); + spansBuilder.add( + Collections.singleton("attribute"), + attributesMatcher.end(GROUP_ATTRIBUTE_NAME) + - attributesMatcher.start(GROUP_ATTRIBUTE_NAME)); + spansBuilder.add( + Collections.singleton("tagmark"), + attributesMatcher.end(GROUP_EQUAL_SYMBOL) + - attributesMatcher.end(GROUP_ATTRIBUTE_NAME)); + spansBuilder.add( + Collections.singleton("avalue"), + attributesMatcher.end(GROUP_ATTRIBUTE_VALUE) + - attributesMatcher.end(GROUP_EQUAL_SYMBOL)); lastKeywordEnd = attributesMatcher.end(); } if (attributesText.length() > lastKeywordEnd) { - spansBuilder.add(Collections.emptyList(), attributesText.length() - lastKeywordEnd); + spansBuilder.add( + Collections.emptyList(), + attributesText.length() - lastKeywordEnd); } } lastKeywordEnd = matcher.end(GROUP_ATTRIBUTES_SECTION); - spansBuilder.add(Collections.singleton("tagmark"), matcher.end(GROUP_CLOSE_BRACKET) - lastKeywordEnd); + spansBuilder.add( + Collections.singleton("tagmark"), + matcher.end(GROUP_CLOSE_BRACKET) - lastKeywordEnd); } } lastKeywordEnd = matcher.end(); @@ -370,7 +448,11 @@ 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); @@ -384,7 +466,6 @@ public void dragDetected(ListProperty sourceList, ObjectProperty< * * @param targetList either availableListView or chosenListView */ - public boolean dragDropped(ListProperty targetList, Dragboard dragboard) { boolean success = false; @@ -397,7 +478,12 @@ public boolean dragDropped(ListProperty targetList, Dragboard dra success = true; if (targetList == availableListProperty) { - targetList.getValue().sort((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName())); + targetList + .getValue() + .sort( + (a, b) -> + a.getDisplayName() + .compareToIgnoreCase(b.getDisplayName())); } } } @@ -410,17 +496,18 @@ public boolean dragDropped(ListProperty targetList, Dragboard dra * * @param targetLayout the Layout, the user drops a layout on */ - public boolean dragDroppedInChosenCell(PreviewLayout targetLayout, Dragboard dragboard) { boolean success = false; if (dragboard.hasContent(DragAndDropDataFormats.PREVIEWLAYOUTS)) { - List draggedSelectedLayouts = new ArrayList<>(localDragboard.getPreviewLayouts()); + List draggedSelectedLayouts = + new ArrayList<>(localDragboard.getPreviewLayouts()); if (!draggedSelectedLayouts.isEmpty()) { 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; @@ -436,14 +523,16 @@ public boolean dragDroppedInChosenCell(PreviewLayout targetLayout, Dragboard dra dragSourceList.getValue().removeAll(draggedSelectedLayouts); if (targetLayout != null) { - targetId = chosenListProperty.getValue().indexOf(targetLayout) + onSelectedDelta; + targetId = + chosenListProperty.getValue().indexOf(targetLayout) + onSelectedDelta; } else if (targetId != 0) { targetId = chosenListProperty.getValue().size(); } chosenListProperty.getValue().addAll(targetId, draggedSelectedLayouts); - draggedSelectedLayouts.forEach(layout -> chosenSelectionModelProperty.getValue().select(layout)); + draggedSelectedLayouts.forEach( + layout -> chosenSelectionModelProperty.getValue().select(layout)); success = true; } @@ -470,8 +559,7 @@ public FilteredList getFilteredAvailableLayouts() { public void setAvailableFilter(String searchTerm) { this.filteredAvailableLayouts.setPredicate( - preview -> searchTerm.isEmpty() - || preview.containsCaseIndependent(searchTerm)); + preview -> searchTerm.isEmpty() || preview.containsCaseIndependent(searchTerm)); } public ObjectProperty> availableSelectionModelProperty() { diff --git a/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java b/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java index 5989cbc6792c..df2341fb7b43 100644 --- a/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java +++ b/src/main/java/org/jabref/gui/preferences/protectedterms/NewProtectedTermsFileDialog.java @@ -1,8 +1,5 @@ package org.jabref.gui.preferences.protectedterms; -import java.util.ArrayList; -import java.util.List; - import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; @@ -18,46 +15,66 @@ import org.jabref.logic.protectedterms.ProtectedTermsList; import org.jabref.logic.util.StandardFileType; +import java.util.ArrayList; +import java.util.List; + 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(); + 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(); Button browse = new Button(Localization.lang("Browse")); - browse.setOnAction(event -> this.dialogService.showFileSaveDialog(fileDialogConfiguration) - .ifPresent(file -> newFile.setText(file.toAbsolutePath().toString()))); + browse.setOnAction( + event -> + this.dialogService + .showFileSaveDialog(fileDialogConfiguration) + .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 - ); - - setResultConverter(button -> { - if (button == ButtonType.OK) { - ProtectedTermsList newList = new ProtectedTermsList(newDescription.getText(), new ArrayList<>(), newFile.getText(), false); - newList.setEnabled(true); - newList.createAndWriteHeading(newDescription.getText()); - termsLists.add(new ProtectedTermsListItemModel(newList)); - } - return null; - }); + getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); + + setResultConverter( + button -> { + if (button == ButtonType.OK) { + ProtectedTermsList newList = + new ProtectedTermsList( + newDescription.getText(), + new ArrayList<>(), + newFile.getText(), + false); + newList.setEnabled(true); + newList.createAndWriteHeading(newDescription.getText()); + termsLists.add(new ProtectedTermsListItemModel(newList)); + } + return null; + }); } } diff --git a/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java b/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java index 733dcec512f5..579d5d77e9a8 100644 --- a/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java +++ b/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTab.java @@ -1,5 +1,9 @@ package org.jabref.gui.preferences.protectedterms; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; + import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.fxml.FXML; import javafx.scene.control.ContextMenu; @@ -20,13 +24,11 @@ import org.jabref.logic.protectedterms.ProtectedTermsList; import org.jabref.logic.protectedterms.ProtectedTermsLoader; -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; - /** * Dialog for managing term list files. */ -public class ProtectedTermsTab extends AbstractPreferenceTabView implements PreferencesTab { +public class ProtectedTermsTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TableView filesTable; @FXML private TableColumn filesTableEnabledColumn; @FXML private TableColumn filesTableDescriptionColumn; @@ -37,9 +39,7 @@ public class ProtectedTermsTab extends AbstractPreferenceTabView() .withContextMenu(this::createContextMenu) .install(filesTable); - filesTableEnabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(filesTableEnabledColumn)); + filesTableEnabledColumn.setCellFactory( + CheckBoxTableCell.forTableColumn(filesTableEnabledColumn)); filesTableEnabledColumn.setCellValueFactory(data -> data.getValue().enabledProperty()); - 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 { - return BindingsHelper.constantOf(list.getLocation()); - } - }); + 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 { + return BindingsHelper.constantOf(list.getLocation()); + } + }); filesTableEditColumn.setCellValueFactory(data -> data.getValue().internalProperty().not()); new ValueTableCellFactory() @@ -74,7 +77,8 @@ public void initialize() { .withOnMouseClickedEvent((item, none) -> event -> viewModel.edit(item)) .install(filesTableEditColumn); - filesTableDeleteColumn.setCellValueFactory(data -> data.getValue().internalProperty().not()); + filesTableDeleteColumn.setCellValueFactory( + data -> data.getValue().internalProperty().not()); new ValueTableCellFactory() .withGraphic(none -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) .withVisibleExpression(ReadOnlyBooleanWrapper::new) @@ -88,12 +92,25 @@ 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"); @@ -119,11 +136,13 @@ public ContextAction(StandardActions command, ProtectedTermsListItemModel itemMo 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 diff --git a/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java b/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java index da345cd066d2..3bb7f0e6ec18 100644 --- a/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/protectedterms/ProtectedTermsTabViewModel.java @@ -1,11 +1,5 @@ package org.jabref.gui.preferences.protectedterms; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; @@ -30,23 +24,30 @@ import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.OptionalUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + 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 +58,10 @@ 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,8 +71,10 @@ 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()); @@ -93,25 +99,36 @@ 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))); - }); + 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))); + }); } 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,18 +137,27 @@ 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) - ); + Optional termsFileType = + OptionalUtil.orElse( + ExternalFileTypes.getExternalFileTypeByExt( + "terms", externalApplicationsPreferences), + ExternalFileTypes.getExternalFileTypeByExt( + "txt", externalApplicationsPreferences)); String fileName = file.getTermsList().getLocation(); try { - NativeDesktop.openExternalFileAnyFormat(new BibDatabaseContext(), externalApplicationsPreferences, filePreferences, fileName, termsFileType); + NativeDesktop.openExternalFileAnyFormat( + new BibDatabaseContext(), + externalApplicationsPreferences, + filePreferences, + fileName, + termsFileType); } catch (IOException e) { LOGGER.warn("Problem open protected terms file editor", e); } @@ -150,12 +176,15 @@ 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)); diff --git a/src/main/java/org/jabref/gui/preferences/table/TableTab.java b/src/main/java/org/jabref/gui/preferences/table/TableTab.java index 37ea2359c397..941d19667637 100644 --- a/src/main/java/org/jabref/gui/preferences/table/TableTab.java +++ b/src/main/java/org/jabref/gui/preferences/table/TableTab.java @@ -1,5 +1,9 @@ package org.jabref.gui.preferences.table; +import com.airhacks.afterburner.views.ViewLoader; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -24,10 +28,8 @@ import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import com.airhacks.afterburner.views.ViewLoader; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; - -public class TableTab extends AbstractPreferenceTabView implements PreferencesTab { +public class TableTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private TableView columnsList; @FXML private TableColumn nameColumn; @@ -49,9 +51,7 @@ public class TableTab extends AbstractPreferenceTabView imple private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); public TableTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -66,7 +66,13 @@ 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() { @@ -83,17 +89,22 @@ private void setupTable() { new ValueTableCellFactory() .withGraphic(item -> IconTheme.JabRefIcons.DELETE_ENTRY.getGraphicNode()) .withTooltip(name -> Localization.lang("Remove column") + " " + name) - .withOnMouseClickedEvent(item -> evt -> - viewModel.removeColumn(columnsList.getFocusModel().getFocusedItem())) + .withOnMouseClickedEvent( + item -> + evt -> + viewModel.removeColumn( + columnsList.getFocusModel().getFocusedItem())) .install(actionsColumn); viewModel.selectedColumnModelProperty().setValue(columnsList.getSelectionModel()); - columnsList.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.DELETE) { - viewModel.removeColumn(columnsList.getSelectionModel().getSelectedItem()); - event.consume(); - } - }); + columnsList.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.DELETE) { + viewModel.removeColumn(columnsList.getSelectionModel().getSelectedItem()); + event.consume(); + } + }); columnsList.itemsProperty().bind(viewModel.columnsListProperty()); @@ -103,33 +114,56 @@ private void setupTable() { addColumnName.itemsProperty().bind(viewModel.availableColumnsProperty()); addColumnName.valueProperty().bindBidirectional(viewModel.addColumnProperty()); addColumnName.setConverter(TableTabViewModel.columnNameStringConverter); - addColumnName.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.ENTER) { - viewModel.insertColumnInList(); - event.consume(); - } - }); + addColumnName.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.ENTER) { + viewModel.insertColumnInList(); + event.consume(); + } + }); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.columnsListValidationStatus(), columnsList)); + Platform.runLater( + () -> + validationVisualizer.initVisualization( + viewModel.columnsListValidationStatus(), columnsList)); } private void setupBindings() { - specialFieldsEnable.selectedProperty().bindBidirectional(viewModel.specialFieldsEnabledProperty()); - extraFileColumnsEnable.selectedProperty().bindBidirectional(viewModel.extraFileColumnsEnabledProperty()); - autoResizeColumns.selectedProperty().bindBidirectional(viewModel.autoResizeColumnsProperty()); + specialFieldsEnable + .selectedProperty() + .bindBidirectional(viewModel.specialFieldsEnabledProperty()); + extraFileColumnsEnable + .selectedProperty() + .bindBidirectional(viewModel.extraFileColumnsEnabledProperty()); + autoResizeColumns + .selectedProperty() + .bindBidirectional(viewModel.autoResizeColumnsProperty()); namesNatbib.selectedProperty().bindBidirectional(viewModel.namesNatbibProperty()); nameAsIs.selectedProperty().bindBidirectional(viewModel.nameAsIsProperty()); nameFirstLast.selectedProperty().bindBidirectional(viewModel.nameFirstLastProperty()); nameLastFirst.selectedProperty().bindBidirectional(viewModel.nameLastFirstProperty()); - abbreviationDisabled.selectedProperty().bindBidirectional(viewModel.abbreviationDisabledProperty()); - abbreviationDisabled.disableProperty().bind(namesNatbib.selectedProperty().or(nameAsIs.selectedProperty())); - abbreviationEnabled.selectedProperty().bindBidirectional(viewModel.abbreviationEnabledProperty()); - abbreviationEnabled.disableProperty().bind(namesNatbib.selectedProperty().or(nameAsIs.selectedProperty())); - abbreviationLastNameOnly.selectedProperty().bindBidirectional(viewModel.abbreviationLastNameOnlyProperty()); - abbreviationLastNameOnly.disableProperty().bind(namesNatbib.selectedProperty().or(nameAsIs.selectedProperty())); + abbreviationDisabled + .selectedProperty() + .bindBidirectional(viewModel.abbreviationDisabledProperty()); + abbreviationDisabled + .disableProperty() + .bind(namesNatbib.selectedProperty().or(nameAsIs.selectedProperty())); + abbreviationEnabled + .selectedProperty() + .bindBidirectional(viewModel.abbreviationEnabledProperty()); + abbreviationEnabled + .disableProperty() + .bind(namesNatbib.selectedProperty().or(nameAsIs.selectedProperty())); + abbreviationLastNameOnly + .selectedProperty() + .bindBidirectional(viewModel.abbreviationLastNameOnlyProperty()); + abbreviationLastNameOnly + .disableProperty() + .bind(namesNatbib.selectedProperty().or(nameAsIs.selectedProperty())); } public void updateToCurrentColumnOrder() { diff --git a/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java b/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java index 1df137f28e48..517eedfa9017 100644 --- a/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/table/TableTabViewModel.java @@ -1,6 +1,9 @@ package org.jabref.gui.preferences.table; -import java.util.EnumSet; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -30,33 +33,35 @@ import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.StandardField; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.EnumSet; public class TableTabViewModel implements PreferenceTabViewModel { - static StringConverter columnNameStringConverter = new StringConverter<>() { - @Override - public String toString(MainTableColumnModel object) { - if (object != null) { - return object.getName(); - } else { - return ""; - } - } - - @Override - public MainTableColumnModel fromString(String string) { - return MainTableColumnModel.parse(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 ObjectProperty addColumnProperty = new SimpleObjectProperty<>(); + static StringConverter columnNameStringConverter = + new StringConverter<>() { + @Override + public String toString(MainTableColumnModel object) { + if (object != null) { + return object.getName(); + } else { + return ""; + } + } + + @Override + public MainTableColumnModel fromString(String string) { + return MainTableColumnModel.parse(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 ObjectProperty addColumnProperty = + new SimpleObjectProperty<>(); private final BooleanProperty specialFieldsEnabledProperty = new SimpleBooleanProperty(); private final BooleanProperty extraFileColumnsEnabledProperty = new SimpleBooleanProperty(); private final BooleanProperty autoResizeColumnsProperty = new SimpleBooleanProperty(); @@ -86,29 +91,34 @@ public TableTabViewModel(DialogService dialogService, GuiPreferences preferences this.nameDisplayPreferences = preferences.getNameDisplayPreferences(); this.mainTablePreferences = preferences.getMainTablePreferences(); - specialFieldsEnabledProperty.addListener((observable, oldValue, newValue) -> { - if (newValue) { - insertSpecialFieldColumns(); - } else { - removeSpecialFieldColumns(); - } - }); - - extraFileColumnsEnabledProperty.addListener((observable, oldValue, newValue) -> { - if (newValue) { - insertExtraFileColumns(); - } 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.")))); + specialFieldsEnabledProperty.addListener( + (observable, oldValue, newValue) -> { + if (newValue) { + insertSpecialFieldColumns(); + } else { + removeSpecialFieldColumns(); + } + }); + + extraFileColumnsEnabledProperty.addListener( + (observable, oldValue, newValue) -> { + if (newValue) { + insertExtraFileColumns(); + } 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.")))); } @Override @@ -129,17 +139,22 @@ public void setValues() { new MainTableColumnModel(MainTableColumnModel.Type.GROUPS), new MainTableColumnModel(MainTableColumnModel.Type.GROUP_ICONS), new MainTableColumnModel(MainTableColumnModel.Type.FILES), - new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, StandardField.TIMESTAMP.getName()), - 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, StandardField.TIMESTAMP.getName()), + 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())); EnumSet.allOf(StandardField.class).stream() - .map(Field::getName) - .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, name)) - .forEach(item -> availableColumnsProperty.getValue().add(item)); + .map(Field::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.NORMALFIELD, name)) + .forEach(item -> availableColumnsProperty.getValue().add(item)); if (specialFieldsEnabledProperty.getValue()) { insertSpecialFieldColumns(); @@ -172,26 +187,34 @@ 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)); + .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); + columnsListProperty + .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)); + .map(ExternalFileType::getName) + .map(name -> new MainTableColumnModel(MainTableColumnModel.Type.EXTRAFILE, name)) + .forEach(item -> availableColumnsProperty.getValue().add(item)); } private void removeExtraFileColumns() { - columnsListProperty.getValue().removeIf(column -> column.getType() == MainTableColumnModel.Type.EXTRAFILE); - availableColumnsProperty.getValue().removeIf(column -> column.getType() == MainTableColumnModel.Type.EXTRAFILE); + columnsListProperty + .getValue() + .removeIf(column -> column.getType() == MainTableColumnModel.Type.EXTRAFILE); + availableColumnsProperty + .getValue() + .removeIf(column -> column.getType() == MainTableColumnModel.Type.EXTRAFILE); } public void insertColumnInList() { @@ -199,7 +222,10 @@ 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); } @@ -210,7 +236,8 @@ public void removeColumn(MainTableColumnModel column) { } public void moveColumnUp() { - MainTableColumnModel selectedColumn = selectedColumnModelProperty.getValue().getSelectedItem(); + MainTableColumnModel selectedColumn = + selectedColumnModelProperty.getValue().getSelectedItem(); int row = columnsListProperty.getValue().indexOf(selectedColumn); if ((selectedColumn == null) || (row < 1)) { return; @@ -222,7 +249,8 @@ public void moveColumnUp() { } public void moveColumnDown() { - MainTableColumnModel selectedColumn = selectedColumnModelProperty.getValue().getSelectedItem(); + MainTableColumnModel selectedColumn = + selectedColumnModelProperty.getValue().getSelectedItem(); int row = columnsListProperty.getValue().indexOf(selectedColumn); if ((selectedColumn == null) || (row > (columnsListProperty.getValue().size() - 2))) { return; @@ -268,8 +296,10 @@ 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; diff --git a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java index b15196a59e3e..9befcc483e87 100644 --- a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java +++ b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.java @@ -1,5 +1,8 @@ package org.jabref.gui.preferences.websearch; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.InvalidationListener; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -20,10 +23,8 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.preferences.FetcherApiKey; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; - -public class WebSearchTab extends AbstractPreferenceTabView implements PreferencesTab { +public class WebSearchTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private CheckBox enableWebSearch; @FXML private CheckBox generateNewKeyOnImport; @@ -44,15 +45,17 @@ public class WebSearchTab extends AbstractPreferenceTabView catalogTable; @FXML private TableColumn catalogEnabledColumn; @FXML private TableColumn catalogColumn; public WebSearchTab() { - ViewLoader.view(this) - .root(this) - .load(); + ViewLoader.view(this).root(this).load(); } @Override @@ -64,9 +67,15 @@ public void initialize() { this.viewModel = new WebSearchTabViewModel(preferences, dialogService); enableWebSearch.selectedProperty().bindBidirectional(viewModel.enableWebSearchProperty()); - generateNewKeyOnImport.selectedProperty().bindBidirectional(viewModel.generateKeyOnImportProperty()); - warnAboutDuplicatesOnImport.selectedProperty().bindBidirectional(viewModel.warnAboutDuplicatesOnImportProperty()); - downloadLinkedOnlineFiles.selectedProperty().bindBidirectional(viewModel.shouldDownloadLinkedOnlineFiles()); + generateNewKeyOnImport + .selectedProperty() + .bindBidirectional(viewModel.generateKeyOnImportProperty()); + warnAboutDuplicatesOnImport + .selectedProperty() + .bindBidirectional(viewModel.warnAboutDuplicatesOnImportProperty()); + downloadLinkedOnlineFiles + .selectedProperty() + .bindBidirectional(viewModel.shouldDownloadLinkedOnlineFiles()); keepDownloadUrl.selectedProperty().bindBidirectional(viewModel.shouldKeepDownloadUrl()); grobidEnabled.selectedProperty().bindBidirectional(viewModel.grobidEnabledProperty()); @@ -78,11 +87,12 @@ public void initialize() { useCustomDOIName.disableProperty().bind(useCustomDOI.selectedProperty().not()); new ViewModelTableRowFactory() - .withOnMouseClickedEvent((entry, event) -> { - if (event.getButton() == MouseButton.PRIMARY) { - entry.setEnabled(!entry.isEnabled()); - } - }) + .withOnMouseClickedEvent( + (entry, event) -> { + if (event.getButton() == MouseButton.PRIMARY) { + entry.setEnabled(!entry.isEnabled()); + } + }) .install(catalogTable); catalogColumn.setReorderable(false); @@ -99,18 +109,23 @@ public void initialize() { testCustomApiKey.setDisable(true); - new ViewModelTableRowFactory() - .install(apiKeySelectorTable); - - apiKeySelectorTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if (oldValue != null) { - updateFetcherApiKey(oldValue); - } - if (newValue != null) { - viewModel.selectedApiKeyProperty().setValue(newValue); - testCustomApiKey.disableProperty().bind(newValue.useProperty().not()); - } - }); + new ViewModelTableRowFactory().install(apiKeySelectorTable); + + apiKeySelectorTable + .getSelectionModel() + .selectedItemProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (oldValue != null) { + updateFetcherApiKey(oldValue); + } + if (newValue != null) { + viewModel.selectedApiKeyProperty().setValue(newValue); + testCustomApiKey + .disableProperty() + .bind(newValue.useProperty().not()); + } + }); apiKeyName.setCellValueFactory(param -> param.getValue().nameProperty()); apiKeyName.setCellFactory(TextFieldTableCell.forTableColumn()); @@ -131,22 +146,29 @@ public void initialize() { persistApiKeys.selectedProperty().bindBidirectional(viewModel.getApikeyPersistProperty()); persistApiKeys.disableProperty().bind(viewModel.apiKeyPersistAvailable().not()); - EasyBind.subscribe(viewModel.apiKeyPersistAvailable(), available -> { - if (!available) { - persistentTooltipWrapper.setTooltip(new Tooltip(Localization.lang("Credential store not available."))); - } else { - persistentTooltipWrapper.setTooltip(null); - } - }); + EasyBind.subscribe( + viewModel.apiKeyPersistAvailable(), + available -> { + if (!available) { + persistentTooltipWrapper.setTooltip( + new Tooltip(Localization.lang("Credential store not available."))); + } else { + persistentTooltipWrapper.setTooltip(null); + } + }); apiKeySelectorTable.setItems(viewModel.fetcherApiKeys()); // Content is set later - viewModel.fetcherApiKeys().addListener((InvalidationListener) change -> { - if (!apiKeySelectorTable.getItems().isEmpty()) { - apiKeySelectorTable.getSelectionModel().selectFirst(); - } - }); + viewModel + .fetcherApiKeys() + .addListener( + (InvalidationListener) + change -> { + if (!apiKeySelectorTable.getItems().isEmpty()) { + apiKeySelectorTable.getSelectionModel().selectFirst(); + } + }); } private void updateFetcherApiKey(FetcherApiKey apiKey) { diff --git a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java index 81cc3a2a11ac..4d0be90ddd62 100644 --- a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTabViewModel.java @@ -1,10 +1,5 @@ package org.jabref.gui.preferences.websearch; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.Optional; -import java.util.stream.Collectors; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.ObjectProperty; @@ -17,6 +12,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import kong.unirest.core.UnirestException; + import org.jabref.gui.DialogService; import org.jabref.gui.preferences.PreferenceTabViewModel; import org.jabref.gui.slr.StudyCatalogItem; @@ -35,7 +32,10 @@ import org.jabref.logic.preferences.DOIPreferences; import org.jabref.logic.preferences.FetcherApiKey; -import kong.unirest.core.UnirestException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.Optional; +import java.util.stream.Collectors; public class WebSearchTabViewModel implements PreferenceTabViewModel { private final BooleanProperty enableWebSearchProperty = new SimpleBooleanProperty(); @@ -52,7 +52,8 @@ public class WebSearchTabViewModel implements PreferenceTabViewModel { private final StringProperty grobidURLProperty = new SimpleStringProperty(""); private final ListProperty apiKeys = new SimpleListProperty<>(); - private final ObjectProperty selectedApiKeyProperty = new SimpleObjectProperty<>(); + private final ObjectProperty selectedApiKeyProperty = + new SimpleObjectProperty<>(); private final BooleanProperty apikeyPersistProperty = new SimpleBooleanProperty(); private final BooleanProperty apikeyPersistAvailableProperty = new SimpleBooleanProperty(); @@ -78,7 +79,8 @@ public WebSearchTabViewModel(CliPreferences preferences, DialogService dialogSer public void setValues() { enableWebSearchProperty.setValue(importerPreferences.areImporterEnabled()); generateKeyOnImportProperty.setValue(importerPreferences.isGenerateNewKeyOnImport()); - warnAboutDuplicatesOnImportProperty.setValue(importerPreferences.shouldWarnAboutDuplicatesOnImport()); + warnAboutDuplicatesOnImportProperty.setValue( + importerPreferences.shouldWarnAboutDuplicatesOnImport()); shouldDownloadLinkedOnlineFiles.setValue(filePreferences.shouldDownloadLinkedFiles()); shouldkeepDownloadUrl.setValue(filePreferences.shouldKeepDownloadUrl()); useCustomDOIProperty.setValue(doiPreferences.isUseCustom()); @@ -87,25 +89,32 @@ public void setValues() { grobidEnabledProperty.setValue(grobidPreferences.isGrobidEnabled()); grobidURLProperty.setValue(grobidPreferences.getGrobidURL()); - apiKeys.setValue(FXCollections.observableArrayList(preferences.getImporterPreferences().getApiKeys())); + apiKeys.setValue( + FXCollections.observableArrayList( + preferences.getImporterPreferences().getApiKeys())); apikeyPersistAvailableProperty.setValue(OS.isKeyringAvailable()); - apikeyPersistProperty.setValue(preferences.getImporterPreferences().shouldPersistCustomKeys()); - catalogs.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) - .stream() - .map(SearchBasedFetcher::getName) - .filter(name -> !name.equals(CompositeSearchBasedFetcher.FETCHER_NAME)) - .map(name -> { - boolean enabled = importerPreferences.getCatalogs().contains(name); - return new StudyCatalogItem(name, enabled); - }) - .toList()); + apikeyPersistProperty.setValue( + preferences.getImporterPreferences().shouldPersistCustomKeys()); + catalogs.addAll( + WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) + .stream() + .map(SearchBasedFetcher::getName) + .filter(name -> !name.equals(CompositeSearchBasedFetcher.FETCHER_NAME)) + .map( + name -> { + boolean enabled = + importerPreferences.getCatalogs().contains(name); + return new StudyCatalogItem(name, enabled); + }) + .toList()); } @Override public void storeSettings() { importerPreferences.setImporterEnabled(enableWebSearchProperty.getValue()); importerPreferences.setGenerateNewKeyOnImport(generateKeyOnImportProperty.getValue()); - importerPreferences.setWarnAboutDuplicatesOnImport(warnAboutDuplicatesOnImportProperty.getValue()); + importerPreferences.setWarnAboutDuplicatesOnImport( + warnAboutDuplicatesOnImportProperty.getValue()); filePreferences.setDownloadLinkedFiles(shouldDownloadLinkedOnlineFiles.getValue()); filePreferences.setKeepDownloadUrl(shouldkeepDownloadUrl.getValue()); grobidPreferences.setGrobidEnabled(grobidEnabledProperty.getValue()); @@ -114,10 +123,11 @@ public void storeSettings() { doiPreferences.setUseCustom(useCustomDOIProperty.get()); doiPreferences.setDefaultBaseURI(useCustomDOINameProperty.getValue().trim()); importerPreferences.setCatalogs( - FXCollections.observableList(catalogs.stream() - .filter(StudyCatalogItem::isEnabled) - .map(StudyCatalogItem::getName) - .collect(Collectors.toList()))); + FXCollections.observableList( + catalogs.stream() + .filter(StudyCatalogItem::isEnabled) + .map(StudyCatalogItem::getName) + .collect(Collectors.toList()))); importerPreferences.setPersistCustomKeys(apikeyPersistProperty.get()); preferences.getImporterPreferences().getApiKeys().clear(); if (apikeyPersistAvailableProperty.get()) { @@ -186,11 +196,11 @@ public void checkCustomApiKey() { final Optional fetcherOpt = WebFetchers.getCustomizableKeyFetchers( - preferences.getImportFormatPreferences(), - preferences.getImporterPreferences()) - .stream() - .filter(fetcher -> fetcher.getName().equals(apiKeyName)) - .findFirst(); + preferences.getImportFormatPreferences(), + preferences.getImporterPreferences()) + .stream() + .filter(fetcher -> fetcher.getName().equals(apiKeyName)) + .findFirst(); if (fetcherOpt.isEmpty()) { dialogService.showErrorDialogAndWait( @@ -214,8 +224,11 @@ 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...). - int statusCode = ((HttpURLConnection) urlDownload.getSource().openConnection()).getResponseCode(); + // 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) { keyValid = false; @@ -225,9 +238,13 @@ public void checkCustomApiKey() { } if (keyValid) { - dialogService.showInformationDialogAndWait(Localization.lang("Check %0 API Key Setting", apiKeyName), Localization.lang("Connection successful!")); + 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.showErrorDialogAndWait( + Localization.lang("Check %0 API Key Setting", apiKeyName), + Localization.lang("Connection failed!")); } } diff --git a/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java b/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java index ec486b606fba..2557736002bc 100644 --- a/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java +++ b/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTab.java @@ -1,6 +1,10 @@ package org.jabref.gui.preferences.xmp; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.fxml.FXML; @@ -24,11 +28,10 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.entry.field.Field; -import com.airhacks.afterburner.views.ViewLoader; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; +import javax.swing.undo.UndoManager; -public class XmpPrivacyTab extends AbstractPreferenceTabView implements PreferencesTab { +public class XmpPrivacyTab extends AbstractPreferenceTabView + implements PreferencesTab { @FXML private CheckBox enableXmpFilter; @FXML private TableView filterList; @@ -43,9 +46,7 @@ public class XmpPrivacyTab extends AbstractPreferenceTabView BindingsHelper.constantOf(cellData.getValue())); + 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())) + item -> + evt -> + viewModel.removeFilter( + filterList.getFocusModel().getFocusedItem())) .install(actionsColumn); - filterList.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.DELETE) { - viewModel.removeFilter(filterList.getSelectionModel().getSelectedItem()); - event.consume(); - } - }); + filterList.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.DELETE) { + viewModel.removeFilter(filterList.getSelectionModel().getSelectedItem()); + event.consume(); + } + }); filterList.itemsProperty().bind(viewModel.filterListProperty()); @@ -94,15 +101,20 @@ public void initialize() { addFieldName.itemsProperty().bind(viewModel.availableFieldsProperty()); addFieldName.valueProperty().bindBidirectional(viewModel.addFieldNameProperty()); addFieldName.setConverter(FieldsUtil.FIELD_STRING_CONVERTER); - addFieldName.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.ENTER) { - viewModel.addField(); - event.consume(); - } - }); + addFieldName.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (event.getCode() == KeyCode.ENTER) { + viewModel.addField(); + event.consume(); + } + }); validationVisualizer.setDecoration(new IconValidationDecorator()); - Platform.runLater(() -> validationVisualizer.initVisualization(viewModel.xmpFilterListValidationStatus(), filterList)); + Platform.runLater( + () -> + validationVisualizer.initVisualization( + viewModel.xmpFilterListValidationStatus(), filterList)); } public void addField() { diff --git a/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java b/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java index d724eb96234b..fdce1b48e204 100644 --- a/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/xmp/XmpPrivacyTabViewModel.java @@ -1,6 +1,9 @@ package org.jabref.gui.preferences.xmp; -import java.util.Comparator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; @@ -17,16 +20,15 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; +import java.util.Comparator; 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; @@ -38,13 +40,16 @@ 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 +76,10 @@ 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 +97,10 @@ 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; diff --git a/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java b/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java index a47915c206cf..21a7742539ee 100644 --- a/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java +++ b/src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java @@ -1,9 +1,7 @@ package org.jabref.gui.preview; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; +import com.airhacks.afterburner.injection.Injector; +import com.google.common.annotations.VisibleForTesting; import javafx.scene.input.ClipboardContent; @@ -21,8 +19,10 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; -import com.airhacks.afterburner.injection.Injector; -import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; public class ClipboardContentGenerator { @@ -30,16 +30,22 @@ public class ClipboardContentGenerator { private final LayoutFormatterPreferences layoutFormatterPreferences; private final JournalAbbreviationRepository abbreviationRepository; - public ClipboardContentGenerator(PreviewPreferences previewPreferences, - LayoutFormatterPreferences layoutFormatterPreferences, - JournalAbbreviationRepository abbreviationRepository) { + public ClipboardContentGenerator( + PreviewPreferences previewPreferences, + LayoutFormatterPreferences layoutFormatterPreferences, + JournalAbbreviationRepository abbreviationRepository) { this.previewPreferences = previewPreferences; this.layoutFormatterPreferences = layoutFormatterPreferences; this.abbreviationRepository = abbreviationRepository; } - public ClipboardContent generate(List selectedEntries, CitationStyleOutputFormat outputFormat, BibDatabaseContext bibDatabaseContext) throws IOException { - List citations = generateCitations(selectedEntries, outputFormat, bibDatabaseContext); + public ClipboardContent generate( + List selectedEntries, + CitationStyleOutputFormat outputFormat, + BibDatabaseContext bibDatabaseContext) + throws IOException { + List citations = + generateCitations(selectedEntries, outputFormat, bibDatabaseContext); PreviewLayout previewLayout = previewPreferences.getSelectedPreviewLayout(); // if it is not a citation style take care of the preview @@ -55,8 +61,13 @@ public ClipboardContent generate(List selectedEntries, CitationStyleOu } } - 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(); @@ -85,7 +96,8 @@ private List generateCitations(List selectedEntries, CitationS static ClipboardContent processPreview(List citations) { ClipboardContent content = new ClipboardContent(); content.putHtml(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations)); - content.putString(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations)); + content.putString( + String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations)); return content; } @@ -95,7 +107,8 @@ static ClipboardContent processPreview(List citations) { @VisibleForTesting static ClipboardContent processText(List citations) { ClipboardContent content = new ClipboardContent(); - content.putString(String.join(CitationStyleOutputFormat.TEXT.getLineSeparator(), citations)); + content.putString( + String.join(CitationStyleOutputFormat.TEXT.getLineSeparator(), citations)); return content; } @@ -105,17 +118,23 @@ static ClipboardContent processText(List citations) { */ @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,10 +142,18 @@ static ClipboardContent processHtml(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(); - StringReader customLayoutReader = new StringReader(customPreviewLayout.getText().replace("__NEWLINE__", "\n")); - Layout layout = new LayoutHelper(customLayoutReader, layoutFormatterPreferences, abbreviationRepository).getLayoutFromText(); + StringReader customLayoutReader = + new StringReader(customPreviewLayout.getText().replace("__NEWLINE__", "\n")); + 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())); diff --git a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java index e8364ca1a3f7..4ec5427a144f 100644 --- a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java +++ b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java @@ -1,8 +1,5 @@ package org.jabref.gui.preview; -import java.io.IOException; -import java.util.List; - import javafx.scene.input.ClipboardContent; import org.jabref.gui.ClipBoardManager; @@ -17,10 +14,12 @@ import org.jabref.logic.util.BackgroundTask; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.BibEntry; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.List; + /** * 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. */ @@ -37,20 +36,25 @@ public class CopyCitationAction extends SimpleCommand { 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 +62,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()))); + dialogService.notify( + Localization.lang("Copied %0 citations.", String.valueOf(selectedEntries.size()))); } } diff --git a/src/main/java/org/jabref/gui/preview/PreviewPanel.java b/src/main/java/org/jabref/gui/preview/PreviewPanel.java index 1120ad4fc612..ab42e99ed005 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/preview/PreviewPanel.java @@ -1,11 +1,5 @@ package org.jabref.gui.preview; -import java.io.File; -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; @@ -31,10 +25,15 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.search.SearchQuery; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + public class PreviewPanel extends VBox { private static final Logger LOGGER = LoggerFactory.getLogger(PreviewPanel.class); @@ -46,64 +45,85 @@ public class PreviewPanel extends VBox { private final DialogService dialogService; private BibEntry entry; - public PreviewPanel(BibDatabaseContext database, - DialogService dialogService, - KeyBindingRepository keyBindingRepository, - GuiPreferences preferences, - ThemeManager themeManager, - TaskExecutor taskExecutor, - LuceneManager luceneManager, - OptionalObjectProperty searchQueryProperty) { + public PreviewPanel( + BibDatabaseContext database, + DialogService dialogService, + KeyBindingRepository keyBindingRepository, + GuiPreferences preferences, + ThemeManager themeManager, + TaskExecutor taskExecutor, + LuceneManager luceneManager, + OptionalObjectProperty searchQueryProperty) { this.keyBindingRepository = keyBindingRepository; this.dialogService = dialogService; this.previewPreferences = preferences.getPreviewPreferences(); - this.fileLinker = new ExternalFilesEntryLinker(preferences.getExternalApplicationsPreferences(), preferences.getFilePreferences(), database, dialogService); + this.fileLinker = + new ExternalFilesEntryLinker( + preferences.getExternalApplicationsPreferences(), + preferences.getFilePreferences(), + database, + dialogService); PreviewPreferences previewPreferences = preferences.getPreviewPreferences(); - previewView = new PreviewViewer(database, dialogService, preferences, themeManager, taskExecutor, searchQueryProperty); + previewView = + new PreviewViewer( + database, + dialogService, + preferences, + themeManager, + taskExecutor, + searchQueryProperty); previewView.setLayout(previewPreferences.getSelectedPreviewLayout()); previewView.setContextMenu(createPopupMenu()); - previewView.setOnDragDetected(event -> { - previewView.startFullDrag(); - - Dragboard dragboard = previewView.startDragAndDrop(TransferMode.COPY); - ClipboardContent content = new ClipboardContent(); - content.putHtml(previewView.getSelectionHtmlContent()); - dragboard.setContent(content); - - event.consume(); - }); - - previewView.setOnDragOver(event -> { - if (event.getDragboard().hasFiles()) { - event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); - } - event.consume(); - }); - - previewView.setOnDragDropped(event -> { - boolean success = false; - if (event.getDragboard().hasContent(DataFormat.FILES)) { - List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); - - if (event.getTransferMode() == TransferMode.MOVE) { - LOGGER.debug("Mode MOVE"); // shift on win or no modifier - fileLinker.moveFilesToFileDirRenameAndAddToEntry(entry, files, luceneManager); - } - if (event.getTransferMode() == TransferMode.LINK) { - LOGGER.debug("Node LINK"); // alt on win - fileLinker.addFilesToEntry(entry, files); - } - if (event.getTransferMode() == TransferMode.COPY) { - LOGGER.debug("Mode Copy"); // ctrl on win, no modifier on Xubuntu - fileLinker.copyFilesToFileDirAndAddToEntry(entry, files, luceneManager); - } - success = true; - } - - event.setDropCompleted(success); - event.consume(); - }); + previewView.setOnDragDetected( + event -> { + previewView.startFullDrag(); + + Dragboard dragboard = previewView.startDragAndDrop(TransferMode.COPY); + ClipboardContent content = new ClipboardContent(); + content.putHtml(previewView.getSelectionHtmlContent()); + dragboard.setContent(content); + + event.consume(); + }); + + previewView.setOnDragOver( + event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes( + TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); + } + event.consume(); + }); + + previewView.setOnDragDropped( + event -> { + boolean success = false; + if (event.getDragboard().hasContent(DataFormat.FILES)) { + List files = + event.getDragboard().getFiles().stream() + .map(File::toPath) + .collect(Collectors.toList()); + + if (event.getTransferMode() == TransferMode.MOVE) { + LOGGER.debug("Mode MOVE"); // shift on win or no modifier + fileLinker.moveFilesToFileDirRenameAndAddToEntry( + entry, files, luceneManager); + } + if (event.getTransferMode() == TransferMode.LINK) { + LOGGER.debug("Node LINK"); // alt on win + fileLinker.addFilesToEntry(entry, files); + } + if (event.getTransferMode() == TransferMode.COPY) { + LOGGER.debug("Mode Copy"); // ctrl on win, no modifier on Xubuntu + fileLinker.copyFilesToFileDirAndAddToEntry(entry, files, luceneManager); + } + success = true; + } + + event.setDropCompleted(success); + event.consume(); + }); this.getChildren().add(previewView); createKeyBindings(); @@ -111,30 +131,44 @@ public PreviewPanel(BibDatabaseContext database, } private void createKeyBindings() { - previewView.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); - if (keyBinding.isPresent()) { - if (keyBinding.get() == KeyBinding.COPY_PREVIEW) { - previewView.copyPreviewToClipBoard(); - event.consume(); - } - } - }); + previewView.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); + if (keyBinding.isPresent()) { + if (keyBinding.get() == KeyBinding.COPY_PREVIEW) { + previewView.copyPreviewToClipBoard(); + event.consume(); + } + } + }); } private ContextMenu createPopupMenu() { - MenuItem copyPreview = new MenuItem(Localization.lang("Copy preview"), IconTheme.JabRefIcons.COPY.getGraphicNode()); - keyBindingRepository.getKeyCombination(KeyBinding.COPY_PREVIEW).ifPresent(copyPreview::setAccelerator); + MenuItem copyPreview = + new MenuItem( + Localization.lang("Copy preview"), + IconTheme.JabRefIcons.COPY.getGraphicNode()); + keyBindingRepository + .getKeyCombination(KeyBinding.COPY_PREVIEW) + .ifPresent(copyPreview::setAccelerator); copyPreview.setOnAction(event -> previewView.copyPreviewToClipBoard()); MenuItem copySelection = new MenuItem(Localization.lang("Copy selection")); copySelection.setOnAction(event -> 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(event -> 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(event -> 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(event -> this.nextPreviewStyle()); ContextMenu menu = new ContextMenu(); @@ -170,6 +204,7 @@ private void cyclePreview(int newPosition) { PreviewLayout layout = previewPreferences.getSelectedPreviewLayout(); previewView.setLayout(layout); - dialogService.notify(Localization.lang("Preview style changed to: %0", layout.getDisplayName())); + dialogService.notify( + Localization.lang("Preview style changed to: %0", layout.getDisplayName())); } } diff --git a/src/main/java/org/jabref/gui/preview/PreviewPreferences.java b/src/main/java/org/jabref/gui/preview/PreviewPreferences.java index 85b98a01ec43..8a5f8ebeef9b 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewPreferences.java +++ b/src/main/java/org/jabref/gui/preview/PreviewPreferences.java @@ -1,8 +1,5 @@ package org.jabref.gui.preview; -import java.nio.file.Path; -import java.util.List; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; @@ -17,6 +14,9 @@ import org.jabref.logic.layout.TextBasedPreviewLayout; import org.jabref.logic.preview.PreviewLayout; +import java.nio.file.Path; +import java.util.List; + public class PreviewPreferences { private final ObservableList layoutCycle; @@ -27,13 +27,14 @@ public class PreviewPreferences { 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); diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 26322517004a..533286b69772 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -1,12 +1,6 @@ package org.jabref.gui.preview; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.net.MalformedURLException; -import java.util.Objects; -import java.util.Optional; -import java.util.regex.Pattern; +import com.airhacks.afterburner.injection.Injector; import javafx.beans.InvalidationListener; import javafx.beans.Observable; @@ -32,8 +26,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.search.SearchQuery; - -import com.airhacks.afterburner.injection.Injector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -42,6 +34,14 @@ import org.w3c.dom.events.EventTarget; import org.w3c.dom.html.HTMLAnchorElement; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; + /** * Displays an BibEntry using the given layout format. */ @@ -50,7 +50,8 @@ public class PreviewViewer extends ScrollPane implements InvalidationListener { private static final Logger LOGGER = LoggerFactory.getLogger(PreviewViewer.class); // https://stackoverflow.com/questions/5669448/get-selected-texts-html-in-div/5670825#5670825 - private static final String JS_GET_SELECTION_HTML_SCRIPT = """ + private static final String JS_GET_SELECTION_HTML_SCRIPT = + """ function getSelectionHtml() { var html = ""; if (typeof window.getSelection != "undefined") { @@ -106,14 +107,16 @@ function getSelectionHtml() { """; - // This is a string format, and it takes a variable name as an argument to pass to the markInstance.markRegExp() Javascript method. + // This is a string format, and it takes a variable name as an argument to pass to the + // markInstance.markRegExp() Javascript method. private static final String JS_MARK_REG_EXP_CALLBACK = """ {done: function(){ markInstance.markRegExp(%s);} }"""; - // This is a string format, and it takes a variable name as an argument to pass to the markInstance.unmark() Javascript method. + // This is a string format, and it takes a variable name as an argument to pass to the + // markInstance.unmark() Javascript method. private static final String JS_UNMARK_WITH_CALLBACK = """ var markInstance = new Mark(document.getElementById("content")); @@ -125,10 +128,12 @@ function getSelectionHtml() { private final TaskExecutor taskExecutor; private final WebView previewView; private final BibDatabaseContext database; - private final ChangeListener> listener = (queryObservable, queryOldValue, queryNewValue) -> { - searchHighlightPattern = queryNewValue.flatMap(SearchQuery::getJavaScriptPatternForWords); - highlightSearchPattern(); - }; + private final ChangeListener> listener = + (queryObservable, queryOldValue, queryNewValue) -> { + searchHighlightPattern = + queryNewValue.flatMap(SearchQuery::getJavaScriptPatternForWords); + highlightSearchPattern(); + }; private Optional entry = Optional.empty(); private Optional searchHighlightPattern = Optional.empty(); @@ -138,12 +143,13 @@ function getSelectionHtml() { /** * @param database Used for resolving strings and pdf directories for links. */ - public PreviewViewer(BibDatabaseContext database, - DialogService dialogService, - GuiPreferences preferences, - ThemeManager themeManager, - TaskExecutor taskExecutor, - OptionalObjectProperty searchQueryProperty) { + public PreviewViewer( + BibDatabaseContext database, + DialogService dialogService, + GuiPreferences preferences, + ThemeManager themeManager, + TaskExecutor taskExecutor, + OptionalObjectProperty searchQueryProperty) { this.database = Objects.requireNonNull(database); this.dialogService = dialogService; this.clipBoardManager = Injector.instantiateModelOrService(ClipBoardManager.class); @@ -155,50 +161,73 @@ public PreviewViewer(BibDatabaseContext database, setContent(previewView); previewView.setContextMenuEnabled(false); - previewView.getEngine().getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != Worker.State.SUCCEEDED) { - return; - } - - if (!registered) { - searchHighlightPattern = searchQueryProperty.get().flatMap(SearchQuery::getJavaScriptPatternForWords); - searchQueryProperty.addListener(listener); - registered = true; - } - highlightSearchPattern(); - - // https://stackoverflow.com/questions/15555510/javafx-stop-opening-url-in-webview-open-in-browser-instead - NodeList anchorList = previewView.getEngine().getDocument().getElementsByTagName("a"); - for (int i = 0; i < anchorList.getLength(); i++) { - Node node = anchorList.item(i); - EventTarget eventTarget = (EventTarget) node; - eventTarget.addEventListener("click", evt -> { - EventTarget target = evt.getCurrentTarget(); - HTMLAnchorElement anchorElement = (HTMLAnchorElement) target; - String href = anchorElement.getHref(); - if (href != null) { - try { - NativeDesktop.openBrowser(href, preferences.getExternalApplicationsPreferences()); - } catch (MalformedURLException exception) { - LOGGER.error("Invalid URL", exception); - } catch (IOException exception) { - LOGGER.error("Invalid URL Input", exception); - } - } - evt.preventDefault(); - }, false); - } - }); + previewView + .getEngine() + .getLoadWorker() + .stateProperty() + .addListener( + (observable, oldValue, newValue) -> { + if (newValue != Worker.State.SUCCEEDED) { + return; + } + + if (!registered) { + searchHighlightPattern = + searchQueryProperty + .get() + .flatMap(SearchQuery::getJavaScriptPatternForWords); + searchQueryProperty.addListener(listener); + registered = true; + } + highlightSearchPattern(); + + // https://stackoverflow.com/questions/15555510/javafx-stop-opening-url-in-webview-open-in-browser-instead + NodeList anchorList = + previewView.getEngine().getDocument().getElementsByTagName("a"); + for (int i = 0; i < anchorList.getLength(); i++) { + Node node = anchorList.item(i); + EventTarget eventTarget = (EventTarget) node; + eventTarget.addEventListener( + "click", + evt -> { + EventTarget target = evt.getCurrentTarget(); + HTMLAnchorElement anchorElement = + (HTMLAnchorElement) target; + String href = anchorElement.getHref(); + if (href != null) { + try { + NativeDesktop.openBrowser( + href, + preferences + .getExternalApplicationsPreferences()); + } catch (MalformedURLException exception) { + LOGGER.error("Invalid URL", exception); + } catch (IOException exception) { + LOGGER.error("Invalid URL Input", exception); + } + } + evt.preventDefault(); + }, + false); + } + }); themeManager.installCss(previewView.getEngine()); } - public PreviewViewer(BibDatabaseContext database, - DialogService dialogService, - GuiPreferences preferences, - ThemeManager themeManager, - TaskExecutor taskExecutor) { - this(database, dialogService, preferences, themeManager, taskExecutor, OptionalObjectProperty.empty()); + public PreviewViewer( + BibDatabaseContext database, + DialogService dialogService, + GuiPreferences preferences, + ThemeManager themeManager, + TaskExecutor taskExecutor) { + this( + database, + dialogService, + preferences, + themeManager, + taskExecutor, + OptionalObjectProperty.empty()); } private void highlightSearchPattern() { @@ -219,14 +248,17 @@ private void highlightSearchPattern() { */ private static String createJavaScriptRegex(Pattern regex) { String pattern = regex.pattern(); - // Create a JavaScript regular expression literal (https://ecma-international.org/ecma-262/10.0/index.html#sec-literals-regular-expression-literals) - // Forward slashes are reserved to delimit the regular expression body. Hence, they must be escaped. + // Create a JavaScript regular expression literal + // (https://ecma-international.org/ecma-262/10.0/index.html#sec-literals-regular-expression-literals) + // Forward slashes are reserved to delimit the regular expression body. Hence, they must be + // escaped. pattern = UNESCAPED_FORWARD_SLASH.matcher(pattern).replaceAll("\\\\/"); return "/" + pattern + "/gmi"; } 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; } @@ -240,11 +272,12 @@ public void setEntry(BibEntry newEntry) { } // Remove update listener for old entry - entry.ifPresent(oldEntry -> { - for (Observable observable : oldEntry.getObservables()) { - observable.removeListener(this); - } - }); + entry.ifPresent( + oldEntry -> { + for (Observable observable : oldEntry.getObservables()) { + observable.removeListener(this); + } + }); entry = Optional.of(newEntry); @@ -257,42 +290,61 @@ public void setEntry(BibEntry newEntry) { private void update() { if (entry.isEmpty() || (layout == null)) { - // Make sure that the preview panel is not completely white, especially with dark theme on + // Make sure that the preview panel is not completely white, especially with dark theme + // on setPreviewText(""); return; } - Number.serialExportNumber = 1; // Set entry number in case that is included in the preview layout. + Number.serialExportNumber = + 1; // Set entry number in case that is included in the preview layout. final BibEntry theEntry = entry.get(); - BackgroundTask - .wrap(() -> layout.generatePreview(theEntry, database)) - .onRunning(() -> setPreviewText("" + Localization.lang("Processing Citation Style \"%0\"...", layout.getDisplayName()) + "")) + BackgroundTask.wrap(() -> layout.generatePreview(theEntry, database)) + .onRunning( + () -> + setPreviewText( + "" + + Localization.lang( + "Processing Citation Style \"%0\"...", + layout.getDisplayName()) + + "")) .onSuccess(this::setPreviewText) - .onFailure(exception -> { - LOGGER.error("Error while generating citation style", exception); - - // Convert stack trace to a string - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - exception.printStackTrace(printWriter); - String stackTraceString = stringWriter.toString(); - - // Set the preview text with the localized error message and the stack trace - setPreviewText(Localization.lang("Error while generating citation style") + "\n\n" + exception.getLocalizedMessage() + "\n\nBibTeX (internal):\n" + theEntry + "\n\nStack Trace:\n" + stackTraceString); - }) + .onFailure( + exception -> { + LOGGER.error("Error while generating citation style", exception); + + // Convert stack trace to a string + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + exception.printStackTrace(printWriter); + String stackTraceString = stringWriter.toString(); + + // Set the preview text with the localized error message and the stack + // trace + setPreviewText( + Localization.lang("Error while generating citation style") + + "\n\n" + + exception.getLocalizedMessage() + + "\n\nBibTeX (internal):\n" + + theEntry + + "\n\nStack Trace:\n" + + stackTraceString); + }) .executeWith(taskExecutor); } private void setPreviewText(String text) { - String myText = """ + String myText = + """ %s

    %s
    - """.formatted(JS_HIGHLIGHT_FUNCTION, text); + """ + .formatted(JS_HIGHLIGHT_FUNCTION, text); previewView.getEngine().setJavaScriptEnabled(true); previewView.getEngine().loadContent(myText); @@ -306,13 +358,19 @@ public void print() { return; } - BackgroundTask - .wrap(() -> { - job.getJobSettings().setJobName(entry.flatMap(BibEntry::getCitationKey).orElse("NO ENTRY")); - previewView.getEngine().print(job); - job.endJob(); - }) - .onFailure(exception -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), exception)) + BackgroundTask.wrap( + () -> { + job.getJobSettings() + .setJobName( + entry.flatMap(BibEntry::getCitationKey) + .orElse("NO ENTRY")); + previewView.getEngine().print(job); + job.endJob(); + }) + .onFailure( + exception -> + dialogService.showErrorDialogAndWait( + Localization.lang("Could not print preview"), exception)) .executeWith(taskExecutor); } @@ -321,7 +379,11 @@ public void copyPreviewToClipBoard() { ClipboardContent content = new ClipboardContent(); content.putString(document.getElementById("content").getTextContent()); - content.putHtml((String) previewView.getEngine().executeScript("document.documentElement.outerHTML")); + content.putHtml( + (String) + previewView + .getEngine() + .executeScript("document.documentElement.outerHTML")); clipBoardManager.setContent(content); } diff --git a/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java b/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java index 40c04c97a8d5..9c8bb91f4519 100644 --- a/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java +++ b/src/main/java/org/jabref/gui/push/AbstractPushToApplication.java @@ -1,8 +1,6 @@ package org.jabref.gui.push; -import java.io.IOException; -import java.util.List; -import java.util.Optional; +import com.google.common.annotations.VisibleForTesting; import org.jabref.gui.DialogService; import org.jabref.gui.actions.Action; @@ -15,20 +13,27 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.strings.StringUtil; - -import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + /** * Abstract class for pushing entries into different editors. */ public abstract class AbstractPushToApplication implements PushToApplication { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractPushToApplication.class); - protected boolean couldNotCall; // Set to true in case the command could not be executed, e.g., if the file is not found - protected boolean couldNotPush; // Set to true in case the tunnel to the program (if one is used) does not operate - protected boolean notDefined; // Set to true if the corresponding path is not defined in the preferences + protected boolean + couldNotCall; // Set to true in case the command could not be executed, e.g., if the + // file is not found + protected boolean + couldNotPush; // Set to true in case the tunnel to the program (if one is used) does not + // operate + protected boolean + notDefined; // Set to true if the corresponding path is not defined in the preferences protected String commandPath; @@ -61,12 +66,20 @@ public void pushEntries(BibDatabaseContext database, List entries, Str } @VisibleForTesting - protected void pushEntries(BibDatabaseContext database, List entries, String keyString, ProcessBuilder processBuilder) { + protected void pushEntries( + BibDatabaseContext database, + List entries, + String keyString, + ProcessBuilder processBuilder) { couldNotPush = false; couldNotCall = false; notDefined = false; - commandPath = preferences.getPushToApplicationPreferences().getCommandPaths().get(this.getDisplayName()); + commandPath = + preferences + .getPushToApplicationPreferences() + .getCommandPaths() + .get(this.getDisplayName()); // Check if a path to the command has been specified if (StringUtil.isNullOrEmpty(commandPath)) { @@ -79,18 +92,12 @@ protected void pushEntries(BibDatabaseContext database, List entries, if (OS.OS_X) { String[] commands = getCommandLine(keyString); if (commands.length < 3) { - LOGGER.error("Commandline does not contain enough parameters to \"push to application\""); + LOGGER.error( + "Commandline does not contain enough parameters to \"push to application\""); return; } processBuilder.command( - "open", - "-a", - commands[0], - "-n", - "--args", - commands[1], - commands[2] - ); + "open", "-a", commands[0], "-n", "--args", commands[1], commands[2]); processBuilder.start(); } else { processBuilder.command(getCommandLine(keyString)); @@ -117,7 +124,8 @@ public void onOperationCompleted() { Localization.lang("Error pushing entries"), Localization.lang("Could not connect to %0", getDisplayName()) + "."); } else { - dialogService.notify(Localization.lang("Pushed citations to %0", getDisplayName()) + "."); + dialogService.notify( + Localization.lang("Pushed citations to %0", getDisplayName()) + "."); } } @@ -161,8 +169,10 @@ protected String getCiteSuffix() { } @Override - public PushToApplicationSettings getSettings(PushToApplication application, PushToApplicationPreferences preferences) { - return new PushToApplicationSettings(application, dialogService, this.preferences.getFilePreferences(), preferences); + public PushToApplicationSettings getSettings( + PushToApplication application, PushToApplicationPreferences preferences) { + return new PushToApplicationSettings( + application, dialogService, this.preferences.getFilePreferences(), preferences); } protected class PushToApplicationAction implements Action { diff --git a/src/main/java/org/jabref/gui/push/PushToApplication.java b/src/main/java/org/jabref/gui/push/PushToApplication.java index 18e012e904f9..95f379eec025 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplication.java +++ b/src/main/java/org/jabref/gui/push/PushToApplication.java @@ -1,12 +1,12 @@ package org.jabref.gui.push; -import java.util.List; - import org.jabref.gui.actions.Action; import org.jabref.gui.icon.JabRefIcon; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import java.util.List; + /** * Class that defines interaction with an external application in the form of "pushing" selected entries to it. */ @@ -53,7 +53,9 @@ public interface PushToApplication { Action getAction(); - PushToApplicationSettings getSettings(PushToApplication application, PushToApplicationPreferences pushToApplicationPreferences); + PushToApplicationSettings getSettings( + PushToApplication application, + PushToApplicationPreferences pushToApplicationPreferences); String getDelimiter(); } diff --git a/src/main/java/org/jabref/gui/push/PushToApplicationCommand.java b/src/main/java/org/jabref/gui/push/PushToApplicationCommand.java index c5fa4c905eb9..8da81bc2ea0a 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplicationCommand.java +++ b/src/main/java/org/jabref/gui/push/PushToApplicationCommand.java @@ -1,9 +1,9 @@ package org.jabref.gui.push; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; + +import com.tobiasdiez.easybind.EasyBind; import javafx.scene.control.ButtonBase; import javafx.scene.control.MenuItem; @@ -21,13 +21,13 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.strings.StringUtil; - -import com.tobiasdiez.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; -import static org.jabref.gui.actions.ActionHelper.needsEntriesSelected; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; /** * An Action class representing the process of invoking a PushToApplication operation. @@ -45,23 +45,29 @@ public class PushToApplicationCommand extends SimpleCommand { private PushToApplication application; - public PushToApplicationCommand(StateManager stateManager, DialogService dialogService, GuiPreferences preferences, TaskExecutor taskExecutor) { + public PushToApplicationCommand( + 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(), + EasyBind.subscribe( + preferences.getPushToApplicationPreferences().activeApplicationNameProperty(), this::setApplication); this.executable.bind(needsDatabase(stateManager).and(needsEntriesSelected(stateManager))); - this.statusMessage.bind(BindingsHelper.ifThenElse( - this.executable, - "", - Localization.lang("This operation requires one or more entries to be selected."))); + this.statusMessage.bind( + BindingsHelper.ifThenElse( + this.executable, + "", + Localization.lang( + "This operation requires one or more entries to be selected."))); } public void registerReconfigurable(Object node) { @@ -75,22 +81,23 @@ public void registerReconfigurable(Object node) { private void setApplication(String applicationName) { final ActionFactory factory = new ActionFactory(); - PushToApplication application = PushToApplications.getApplicationByName( - applicationName, - dialogService, - preferences) - .orElse(new PushToEmacs(dialogService, preferences)); + PushToApplication application = + PushToApplications.getApplicationByName(applicationName, dialogService, preferences) + .orElse(new PushToEmacs(dialogService, preferences)); - preferences.getPushToApplicationPreferences().setActiveApplicationName(application.getDisplayName()); + preferences + .getPushToApplicationPreferences() + .setActiveApplicationName(application.getDisplayName()); this.application = Objects.requireNonNull(application); - reconfigurableControls.forEach(object -> { - if (object instanceof MenuItem item) { - factory.configureMenuItem(application.getAction(), this, item); - } else if (object instanceof ButtonBase base) { - factory.configureIconButton(application.getAction(), this, base); - } - }); + reconfigurableControls.forEach( + object -> { + if (object instanceof MenuItem item) { + factory.configureMenuItem(application.getAction(), this, item); + } else if (object instanceof ButtonBase base) { + factory.configureIconButton(application.getAction(), this, base); + } + }); } public Action getAction() { @@ -125,7 +132,8 @@ public void execute() { if (StringUtil.isBlank(entry.getCitationKey())) { dialogService.showErrorDialogAndWait( application.getDisplayName(), - Localization.lang("This operation requires all selected entries to have citation keys defined.")); + Localization.lang( + "This operation requires all selected entries to have citation keys defined.")); return; } } @@ -133,13 +141,19 @@ 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() { - BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(() -> new NullPointerException("Database null")); - application.pushEntries(database, stateManager.getSelectedEntries(), getKeyString(stateManager.getSelectedEntries(), application.getDelimiter())); + BibDatabaseContext database = + stateManager + .getActiveDatabase() + .orElseThrow(() -> new NullPointerException("Database null")); + application.pushEntries( + database, + stateManager.getSelectedEntries(), + getKeyString(stateManager.getSelectedEntries(), application.getDelimiter())); } } diff --git a/src/main/java/org/jabref/gui/push/PushToApplicationPreferences.java b/src/main/java/org/jabref/gui/push/PushToApplicationPreferences.java index 7b569f736914..16966c8c51e0 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplicationPreferences.java +++ b/src/main/java/org/jabref/gui/push/PushToApplicationPreferences.java @@ -1,23 +1,24 @@ package org.jabref.gui.push; -import java.util.Map; - import javafx.beans.property.MapProperty; import javafx.beans.property.SimpleMapProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; +import java.util.Map; + public class PushToApplicationPreferences { private final StringProperty activeApplicationName; private final MapProperty commandPaths; private final StringProperty emacsArguments; private final StringProperty vimServer; - public PushToApplicationPreferences(String activeApplicationName, - Map commandPaths, - String emacsArguments, - String vimServer) { + public PushToApplicationPreferences( + String activeApplicationName, + Map commandPaths, + String emacsArguments, + String vimServer) { this.activeApplicationName = new SimpleStringProperty(activeApplicationName); this.commandPaths = new SimpleMapProperty<>(FXCollections.observableMap(commandPaths)); this.emacsArguments = new SimpleStringProperty(emacsArguments); diff --git a/src/main/java/org/jabref/gui/push/PushToApplicationSettings.java b/src/main/java/org/jabref/gui/push/PushToApplicationSettings.java index 26740b27abe6..6f2eee30c1c1 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplicationSettings.java +++ b/src/main/java/org/jabref/gui/push/PushToApplicationSettings.java @@ -1,8 +1,5 @@ package org.jabref.gui.push; -import java.util.HashMap; -import java.util.Map; - import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; @@ -17,6 +14,9 @@ import org.jabref.logic.FilePreferences; import org.jabref.logic.l10n.Localization; +import java.util.HashMap; +import java.util.Map; + public class PushToApplicationSettings { protected final Label commandLabel; @@ -25,10 +25,11 @@ public class PushToApplicationSettings { protected final PushToApplicationPreferences preferences; protected final AbstractPushToApplication application; - public PushToApplicationSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public PushToApplicationSettings( + PushToApplication application, + DialogService dialogService, + FilePreferences filePreferences, + PushToApplicationPreferences preferences) { this.application = (AbstractPushToApplication) application; this.preferences = preferences; @@ -46,8 +47,10 @@ public PushToApplicationSettings(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 - StringBuilder commandLine = new StringBuilder(Localization.lang("Path to %0", application.getDisplayName())); + // 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 { @@ -59,10 +62,15 @@ public PushToApplicationSettings(PushToApplication application, path.setText(preferences.getCommandPaths().get(this.application.getDisplayName())); settingsPane.add(path, 1, 0); - FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .withInitialDirectory(filePreferences.getWorkingDirectory()).build(); - browse.setOnAction(e -> dialogService.showFileOpenDialog(fileDialogConfiguration) - .ifPresent(f -> path.setText(f.toAbsolutePath().toString()))); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .withInitialDirectory(filePreferences.getWorkingDirectory()) + .build(); + browse.setOnAction( + e -> + dialogService + .showFileOpenDialog(fileDialogConfiguration) + .ifPresent(f -> path.setText(f.toAbsolutePath().toString()))); settingsPane.add(browse, 2, 0); ColumnConstraints textConstraints = new ColumnConstraints(); @@ -70,7 +78,9 @@ public PushToApplicationSettings(PushToApplication application, pathConstraints.setHgrow(Priority.ALWAYS); ColumnConstraints browseConstraints = new ColumnConstraints(20.0); browseConstraints.setHgrow(Priority.NEVER); - settingsPane.getColumnConstraints().addAll(textConstraints, pathConstraints, browseConstraints); + settingsPane + .getColumnConstraints() + .addAll(textConstraints, pathConstraints, browseConstraints); } /** diff --git a/src/main/java/org/jabref/gui/push/PushToApplications.java b/src/main/java/org/jabref/gui/push/PushToApplications.java index 08b551674a79..6374244f5673 100644 --- a/src/main/java/org/jabref/gui/push/PushToApplications.java +++ b/src/main/java/org/jabref/gui/push/PushToApplications.java @@ -1,12 +1,12 @@ package org.jabref.gui.push; +import org.jabref.gui.DialogService; +import org.jabref.gui.preferences.GuiPreferences; + import java.util.ArrayList; import java.util.List; import java.util.Optional; -import org.jabref.gui.DialogService; -import org.jabref.gui.preferences.GuiPreferences; - public class PushToApplications { public static final String EMACS = "Emacs"; @@ -21,31 +21,33 @@ public class PushToApplications { private static final List APPLICATIONS = new ArrayList<>(); - private PushToApplications() { - } + private PushToApplications() {} - public static List getAllApplications(DialogService dialogService, GuiPreferences preferences) { + public static List getAllApplications( + DialogService dialogService, GuiPreferences preferences) { if (!APPLICATIONS.isEmpty()) { return APPLICATIONS; } - APPLICATIONS.addAll(List.of( - new PushToEmacs(dialogService, preferences), - new PushToLyx(dialogService, preferences), - new PushToSublimeText(dialogService, preferences), - new PushToTexmaker(dialogService, preferences), - new PushToTeXstudio(dialogService, preferences), - new PushToTeXworks(dialogService, preferences), - new PushToVim(dialogService, preferences), - new PushToWinEdt(dialogService, preferences), - new PushToTexShop(dialogService, preferences))); + APPLICATIONS.addAll( + List.of( + new PushToEmacs(dialogService, preferences), + new PushToLyx(dialogService, preferences), + new PushToSublimeText(dialogService, preferences), + new PushToTexmaker(dialogService, preferences), + new PushToTeXstudio(dialogService, preferences), + new PushToTeXworks(dialogService, preferences), + new PushToVim(dialogService, preferences), + new PushToWinEdt(dialogService, preferences), + new PushToTexShop(dialogService, preferences))); return APPLICATIONS; } - public static Optional getApplicationByName(String applicationName, DialogService dialogService, GuiPreferences preferences) { + public static Optional getApplicationByName( + String applicationName, DialogService dialogService, GuiPreferences preferences) { return getAllApplications(dialogService, preferences).stream() - .filter(application -> application.getDisplayName().equals(applicationName)) - .findAny(); + .filter(application -> application.getDisplayName().equals(applicationName)) + .findAny(); } } diff --git a/src/main/java/org/jabref/gui/push/PushToEmacs.java b/src/main/java/org/jabref/gui/push/PushToEmacs.java index c3d612b2d48b..8edac279f1db 100644 --- a/src/main/java/org/jabref/gui/push/PushToEmacs.java +++ b/src/main/java/org/jabref/gui/push/PushToEmacs.java @@ -1,10 +1,5 @@ package org.jabref.gui.push; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; @@ -14,10 +9,14 @@ import org.jabref.logic.util.HeadlessExecutorService; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + public class PushToEmacs extends AbstractPushToApplication { public static final String NAME = PushToApplications.EMACS; @@ -47,7 +46,8 @@ public void pushEntries(BibDatabaseContext database, List entries, Str couldNotCall = false; notDefined = false; - PushToApplicationPreferences pushToApplicationPreferences = preferences.getPushToApplicationPreferences(); + PushToApplicationPreferences pushToApplicationPreferences = + preferences.getPushToApplicationPreferences(); commandPath = pushToApplicationPreferences.getCommandPaths().get(this.getDisplayName()); @@ -74,51 +74,58 @@ public void pushEntries(BibDatabaseContext database, List entries, Str // so cmd receives: (insert \"\\cite{Blah2001}\") // so emacs receives: (insert "\cite{Blah2001}") - com[com.length - 1] = prefix.concat("\"" - + getCitePrefix().replace("\\", "\\\\") - + keys - + getCiteSuffix().replace("\\", "\\\\") - + "\"").concat(suffix) - .replace("\"", "\\\""); + com[com.length - 1] = + prefix.concat( + "\"" + + getCitePrefix().replace("\\", "\\\\") + + keys + + getCiteSuffix().replace("\\", "\\\\") + + "\"") + .concat(suffix) + .replace("\"", "\\\""); } else { // Linux gnuclient/emacslient escaping: // java string: "(insert \"\\\\cite{Blah2001}\")" // so sh receives: (insert "\\cite{Blah2001}") // so emacs receives: (insert "\cite{Blah2001}") - com[com.length - 1] = prefix.concat("\"" - + getCitePrefix().replace("\\", "\\\\") - + keys - + getCiteSuffix().replace("\\", "\\\\") - + "\"").concat(suffix); + com[com.length - 1] = + prefix.concat( + "\"" + + getCitePrefix().replace("\\", "\\\\") + + keys + + getCiteSuffix().replace("\\", "\\\\") + + "\"") + .concat(suffix); } LOGGER.atDebug() - .setMessage("Executing command {}") - .addArgument(() -> Arrays.toString(com)) - .log(); + .setMessage("Executing command {}") + .addArgument(() -> Arrays.toString(com)) + .log(); final Process p = Runtime.getRuntime().exec(com); - HeadlessExecutorService.INSTANCE.executeAndWait(() -> { - try (InputStream out = p.getErrorStream()) { - int c; - StringBuilder sb = new StringBuilder(); - try { - while ((c = out.read()) != -1) { - sb.append((char) c); + HeadlessExecutorService.INSTANCE.executeAndWait( + () -> { + try (InputStream out = p.getErrorStream()) { + int c; + StringBuilder sb = new StringBuilder(); + try { + while ((c = out.read()) != -1) { + sb.append((char) c); + } + } catch (IOException e) { + LOGGER.warn("Could not read from stderr.", e); + } + // Error stream has been closed. See if there were any errors: + if (!sb.toString().trim().isEmpty()) { + LOGGER.warn("Push to Emacs error: {}", sb); + couldNotPush = true; + } + } catch (IOException e) { + LOGGER.warn("Error handling std streams", e); } - } catch (IOException e) { - LOGGER.warn("Could not read from stderr.", e); - } - // Error stream has been closed. See if there were any errors: - if (!sb.toString().trim().isEmpty()) { - LOGGER.warn("Push to Emacs error: {}", sb); - couldNotPush = true; - } - } catch (IOException e) { - LOGGER.warn("Error handling std streams", e); - } - }); + }); } catch (IOException excep) { LOGGER.warn("Problem pushing to Emacs.", excep); couldNotCall = true; @@ -128,10 +135,12 @@ public void pushEntries(BibDatabaseContext database, List entries, Str @Override public void onOperationCompleted() { if (couldNotPush) { - dialogService.showErrorDialogAndWait(Localization.lang("Error pushing entries"), + dialogService.showErrorDialogAndWait( + Localization.lang("Error pushing entries"), Localization.lang("Could not push to a running emacs daemon.")); } else if (couldNotCall) { - dialogService.showErrorDialogAndWait(Localization.lang("Error pushing entries"), + dialogService.showErrorDialogAndWait( + Localization.lang("Error pushing entries"), Localization.lang("Could not run the emacs client.")); } else { super.onOperationCompleted(); @@ -144,7 +153,9 @@ protected String getCommandName() { } @Override - public PushToApplicationSettings getSettings(PushToApplication application, PushToApplicationPreferences preferences) { - return new PushToEmacsSettings(application, dialogService, this.preferences.getFilePreferences(), preferences); + public PushToApplicationSettings getSettings( + PushToApplication application, PushToApplicationPreferences preferences) { + return new PushToEmacsSettings( + application, dialogService, this.preferences.getFilePreferences(), preferences); } } diff --git a/src/main/java/org/jabref/gui/push/PushToEmacsSettings.java b/src/main/java/org/jabref/gui/push/PushToEmacsSettings.java index 4efcb9be8144..315a71c66ab7 100644 --- a/src/main/java/org/jabref/gui/push/PushToEmacsSettings.java +++ b/src/main/java/org/jabref/gui/push/PushToEmacsSettings.java @@ -11,10 +11,11 @@ public class PushToEmacsSettings extends PushToApplicationSettings { private final TextField additionalParams = new TextField(); - public PushToEmacsSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public PushToEmacsSettings( + PushToApplication application, + DialogService dialogService, + FilePreferences filePreferences, + PushToApplicationPreferences preferences) { super(application, dialogService, filePreferences, preferences); settingsPane.add(new Label(Localization.lang("Additional parameters") + ":"), 0, 1); diff --git a/src/main/java/org/jabref/gui/push/PushToLyx.java b/src/main/java/org/jabref/gui/push/PushToLyx.java index 0bcb7795fc8d..6549b76c9a3a 100644 --- a/src/main/java/org/jabref/gui/push/PushToLyx.java +++ b/src/main/java/org/jabref/gui/push/PushToLyx.java @@ -1,12 +1,5 @@ package org.jabref.gui.push; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; @@ -15,10 +8,16 @@ import org.jabref.logic.util.HeadlessExecutorService; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + public class PushToLyx extends AbstractPushToApplication { public static final String NAME = PushToApplications.LYX; @@ -42,28 +41,39 @@ public JabRefIcon getApplicationIcon() { @Override public void onOperationCompleted() { if (couldNotPush) { - dialogService.showErrorDialogAndWait(Localization.lang("Error pushing entries"), + dialogService.showErrorDialogAndWait( + Localization.lang("Error pushing entries"), Localization.lang("Verify that LyX is running and that the lyxpipe is valid.") - + "[" + commandPath + "]"); + + "[" + + commandPath + + "]"); } else if (couldNotCall) { - dialogService.showErrorDialogAndWait(Localization.lang("Unable to write to %0.", commandPath + ".in")); + dialogService.showErrorDialogAndWait( + Localization.lang("Unable to write to %0.", commandPath + ".in")); } else { super.onOperationCompleted(); } } @Override - public PushToApplicationSettings getSettings(PushToApplication application, PushToApplicationPreferences preferences) { - return new PushToLyxSettings(application, dialogService, this.preferences.getFilePreferences(), preferences); + public PushToApplicationSettings getSettings( + PushToApplication application, PushToApplicationPreferences preferences) { + return new PushToLyxSettings( + application, dialogService, this.preferences.getFilePreferences(), preferences); } @Override - public void pushEntries(BibDatabaseContext database, final List entries, final String keyString) { + public void pushEntries( + BibDatabaseContext database, final List entries, final String keyString) { couldNotPush = false; couldNotCall = false; notDefined = false; - commandPath = preferences.getPushToApplicationPreferences().getCommandPaths().get(this.getDisplayName()); + commandPath = + preferences + .getPushToApplicationPreferences() + .getCommandPaths() + .get(this.getDisplayName()); if ((commandPath == null) || commandPath.trim().isEmpty()) { notDefined = true; @@ -73,7 +83,10 @@ public void pushEntries(BibDatabaseContext database, final List entrie if (!commandPath.endsWith(".in")) { commandPath = commandPath + ".in"; } - File lp = new File(commandPath); // this needs to fixed because it gives "asdf" when going prefs.get("lyxpipe") + File lp = + new File( + commandPath); // this needs to fixed because it gives "asdf" when going + // prefs.get("lyxpipe") if (!lp.exists() || !lp.canWrite()) { // See if it helps to append ".in": lp = new File(commandPath + ".in"); @@ -85,14 +98,16 @@ public void pushEntries(BibDatabaseContext database, final List entrie final File lyxpipe = lp; - HeadlessExecutorService.INSTANCE.executeAndWait(() -> { - try (FileWriter fw = new FileWriter(lyxpipe, StandardCharsets.UTF_8); BufferedWriter lyxOut = new BufferedWriter(fw)) { - String citeStr = "LYXCMD:sampleclient:citation-insert:" + keyString; - lyxOut.write(citeStr + "\n"); - } catch (IOException excep) { - couldNotCall = true; - LOGGER.warn("Problem pushing to LyX/Kile.", excep); - } - }); + HeadlessExecutorService.INSTANCE.executeAndWait( + () -> { + try (FileWriter fw = new FileWriter(lyxpipe, StandardCharsets.UTF_8); + BufferedWriter lyxOut = new BufferedWriter(fw)) { + String citeStr = "LYXCMD:sampleclient:citation-insert:" + keyString; + lyxOut.write(citeStr + "\n"); + } catch (IOException excep) { + couldNotCall = true; + LOGGER.warn("Problem pushing to LyX/Kile.", excep); + } + }); } } diff --git a/src/main/java/org/jabref/gui/push/PushToLyxSettings.java b/src/main/java/org/jabref/gui/push/PushToLyxSettings.java index 86f0a06804fa..dacbc8866d60 100644 --- a/src/main/java/org/jabref/gui/push/PushToLyxSettings.java +++ b/src/main/java/org/jabref/gui/push/PushToLyxSettings.java @@ -6,10 +6,11 @@ public class PushToLyxSettings extends PushToApplicationSettings { - public PushToLyxSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public PushToLyxSettings( + PushToApplication application, + DialogService dialogService, + FilePreferences filePreferences, + PushToApplicationPreferences preferences) { super(application, dialogService, filePreferences, preferences); commandLabel.setText(Localization.lang("Path to LyX pipe") + ":"); diff --git a/src/main/java/org/jabref/gui/push/PushToSublimeText.java b/src/main/java/org/jabref/gui/push/PushToSublimeText.java index af0820e7ab27..00211be64774 100644 --- a/src/main/java/org/jabref/gui/push/PushToSublimeText.java +++ b/src/main/java/org/jabref/gui/push/PushToSublimeText.java @@ -1,10 +1,5 @@ package org.jabref.gui.push; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; @@ -15,10 +10,14 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + public class PushToSublimeText extends AbstractPushToApplication { public static final String NAME = PushToApplications.SUBLIME_TEXT; @@ -45,7 +44,11 @@ public void pushEntries(BibDatabaseContext database, List entries, Str couldNotCall = false; notDefined = false; - commandPath = preferences.getPushToApplicationPreferences().getCommandPaths().get(this.getDisplayName()); + commandPath = + preferences + .getPushToApplicationPreferences() + .getCommandPaths() + .get(this.getDisplayName()); // Check if a path to the command has been specified if (StringUtil.isNullOrEmpty(commandPath)) { @@ -60,8 +63,10 @@ public void pushEntries(BibDatabaseContext database, List entries, Str envs.put("PATH", Path.of(commandPath).getParent().toString()); Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::info); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::info); + StreamGobbler streamGobblerInput = + new StreamGobbler(process.getInputStream(), LOGGER::info); + StreamGobbler streamGobblerError = + new StreamGobbler(process.getErrorStream(), LOGGER::info); HeadlessExecutorService.INSTANCE.execute(streamGobblerInput); HeadlessExecutorService.INSTANCE.execute(streamGobblerError); @@ -81,9 +86,31 @@ protected String[] getCommandLine(String keyString) { if (OS.WINDOWS) { // TODO we might need to escape the inner double quotes with """ """ - return new String[] {"cmd.exe", "/c", "\"" + commandPath + "\"" + "--command \"insert {\\\"characters\\\": \"\\" + getCitePrefix() + keyString + getCiteSuffix() + "\"}\""}; + return new String[] { + "cmd.exe", + "/c", + "\"" + + commandPath + + "\"" + + "--command \"insert {\\\"characters\\\": \"\\" + + getCitePrefix() + + keyString + + getCiteSuffix() + + "\"}\"" + }; } else { - return new String[] {"sh", "-c", "\"" + commandPath + "\"" + " --command 'insert {\"characters\": \"" + citeCommand + keyString + getCiteSuffix() + "\"}'"}; + return new String[] { + "sh", + "-c", + "\"" + + commandPath + + "\"" + + " --command 'insert {\"characters\": \"" + + citeCommand + + keyString + + getCiteSuffix() + + "\"}'" + }; } } } diff --git a/src/main/java/org/jabref/gui/push/PushToTeXstudio.java b/src/main/java/org/jabref/gui/push/PushToTeXstudio.java index 17c6d5650c62..7e8700a578dc 100644 --- a/src/main/java/org/jabref/gui/push/PushToTeXstudio.java +++ b/src/main/java/org/jabref/gui/push/PushToTeXstudio.java @@ -25,6 +25,10 @@ public JabRefIcon getApplicationIcon() { @Override protected String[] getCommandLine(String keyString) { - return new String[] {commandPath, "--insert-cite", "%s%s%s".formatted(getCitePrefix(), keyString, getCiteSuffix())}; + return new String[] { + commandPath, + "--insert-cite", + "%s%s%s".formatted(getCitePrefix(), keyString, getCiteSuffix()) + }; } } diff --git a/src/main/java/org/jabref/gui/push/PushToTeXworks.java b/src/main/java/org/jabref/gui/push/PushToTeXworks.java index c59df18ba0f8..3f8a3cc35fe6 100644 --- a/src/main/java/org/jabref/gui/push/PushToTeXworks.java +++ b/src/main/java/org/jabref/gui/push/PushToTeXworks.java @@ -31,6 +31,10 @@ public JabRefIcon getApplicationIcon() { @Override protected String[] getCommandLine(String keyString) { - return new String[] {commandPath, "--insert-text", "%s%s%s".formatted(getCitePrefix(), keyString, getCiteSuffix())}; + return new String[] { + commandPath, + "--insert-text", + "%s%s%s".formatted(getCitePrefix(), keyString, getCiteSuffix()) + }; } } diff --git a/src/main/java/org/jabref/gui/push/PushToTexShop.java b/src/main/java/org/jabref/gui/push/PushToTexShop.java index 889982ca8ce7..64d605a4d940 100644 --- a/src/main/java/org/jabref/gui/push/PushToTexShop.java +++ b/src/main/java/org/jabref/gui/push/PushToTexShop.java @@ -1,8 +1,5 @@ package org.jabref.gui.push; -import java.io.IOException; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; @@ -13,10 +10,12 @@ import org.jabref.logic.util.HeadlessExecutorService; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.List; + public class PushToTexShop extends AbstractPushToApplication { public static final String NAME = PushToApplications.TEXSHOP; @@ -43,15 +42,21 @@ public void pushEntries(BibDatabaseContext database, List entries, Str couldNotCall = false; notDefined = false; - commandPath = preferences.getPushToApplicationPreferences().getCommandPaths().get(this.getDisplayName()); + commandPath = + preferences + .getPushToApplicationPreferences() + .getCommandPaths() + .get(this.getDisplayName()); try { LOGGER.debug("TexShop string: {}", String.join(" ", getCommandLine(keyString))); ProcessBuilder processBuilder = new ProcessBuilder(getCommandLine(keyString)); processBuilder.inheritIO(); Process process = processBuilder.start(); - StreamGobbler streamGobblerInput = new StreamGobbler(process.getInputStream(), LOGGER::info); - StreamGobbler streamGobblerError = new StreamGobbler(process.getErrorStream(), LOGGER::info); + StreamGobbler streamGobblerInput = + new StreamGobbler(process.getInputStream(), LOGGER::info); + StreamGobbler streamGobblerError = + new StreamGobbler(process.getErrorStream(), LOGGER::info); HeadlessExecutorService.INSTANCE.execute(streamGobblerInput); HeadlessExecutorService.INSTANCE.execute(streamGobblerError); @@ -73,16 +78,23 @@ protected String[] getCommandLine(String keyString) { citeCommand = sb.toString(); } - String osascriptTexShop = "osascript -e 'tell application \"TeXShop\"\n" + - "activate\n" + - "set TheString to \"" + citeCommand + keyString + getCiteSuffix() + "\"\n" + - "set content of selection of front document to TheString\n" + - "end tell'"; + String osascriptTexShop = + "osascript -e 'tell application \"TeXShop\"\n" + + "activate\n" + + "set TheString to \"" + + citeCommand + + keyString + + getCiteSuffix() + + "\"\n" + + "set content of selection of front document to TheString\n" + + "end tell'"; if (OS.OS_X) { return new String[] {"sh", "-c", osascriptTexShop}; } else { - dialogService.showInformationDialogAndWait(Localization.lang("Push to application"), Localization.lang("Pushing citations to TeXShop is only possible on macOS!")); + dialogService.showInformationDialogAndWait( + Localization.lang("Push to application"), + Localization.lang("Pushing citations to TeXShop is only possible on macOS!")); return new String[] {}; } } diff --git a/src/main/java/org/jabref/gui/push/PushToVim.java b/src/main/java/org/jabref/gui/push/PushToVim.java index 8702cd11eedb..4df20019e7ce 100644 --- a/src/main/java/org/jabref/gui/push/PushToVim.java +++ b/src/main/java/org/jabref/gui/push/PushToVim.java @@ -1,10 +1,5 @@ package org.jabref.gui.push; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; - import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.icon.JabRefIcon; @@ -13,10 +8,14 @@ import org.jabref.logic.util.HeadlessExecutorService; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + public class PushToVim extends AbstractPushToApplication { public static final String NAME = PushToApplications.VIM; @@ -38,8 +37,10 @@ public JabRefIcon getApplicationIcon() { } @Override - public PushToApplicationSettings getSettings(PushToApplication application, PushToApplicationPreferences preferences) { - return new PushToVimSettings(application, dialogService, this.preferences.getFilePreferences(), preferences); + public PushToApplicationSettings getSettings( + PushToApplication application, PushToApplicationPreferences preferences) { + return new PushToVimSettings( + application, dialogService, this.preferences.getFilePreferences(), preferences); } @Override @@ -48,7 +49,11 @@ public void pushEntries(BibDatabaseContext database, List entries, Str couldNotCall = false; notDefined = false; - commandPath = preferences.getPushToApplicationPreferences().getCommandPaths().get(this.getDisplayName()); + commandPath = + preferences + .getPushToApplicationPreferences() + .getCommandPaths() + .get(this.getDisplayName()); if ((commandPath == null) || commandPath.trim().isEmpty()) { notDefined = true; @@ -56,37 +61,43 @@ public void pushEntries(BibDatabaseContext database, List entries, Str } try { - String[] com = new String[]{commandPath, "--servername", - preferences.getPushToApplicationPreferences().getVimServer(), "--remote-send", - "a" + getCitePrefix() + keys + getCiteSuffix()}; + String[] com = + new String[] { + commandPath, + "--servername", + preferences.getPushToApplicationPreferences().getVimServer(), + "--remote-send", + "a" + getCitePrefix() + keys + getCiteSuffix() + }; LOGGER.atDebug() - .setMessage("Executing command {}") - .addArgument(() -> Arrays.toString(com)) - .log(); + .setMessage("Executing command {}") + .addArgument(() -> Arrays.toString(com)) + .log(); final Process p = Runtime.getRuntime().exec(com); - HeadlessExecutorService.INSTANCE.executeAndWait(() -> { - try (InputStream out = p.getErrorStream()) { - int c; - StringBuilder sb = new StringBuilder(); - try { - while ((c = out.read()) != -1) { - sb.append((char) c); + HeadlessExecutorService.INSTANCE.executeAndWait( + () -> { + try (InputStream out = p.getErrorStream()) { + int c; + StringBuilder sb = new StringBuilder(); + try { + while ((c = out.read()) != -1) { + sb.append((char) c); + } + } catch (IOException e) { + LOGGER.warn("Could not read from stderr.", e); + } + // Error stream has been closed. See if there were any errors: + if (!sb.toString().trim().isEmpty()) { + LOGGER.warn("Push to Vim error: {}", sb); + couldNotPush = true; + } + } catch (IOException e) { + LOGGER.warn("Error handling std streams", e); } - } catch (IOException e) { - LOGGER.warn("Could not read from stderr.", e); - } - // Error stream has been closed. See if there were any errors: - if (!sb.toString().trim().isEmpty()) { - LOGGER.warn("Push to Vim error: {}", sb); - couldNotPush = true; - } - } catch (IOException e) { - LOGGER.warn("Error handling std streams", e); - } - }); + }); } catch (IOException excep) { LOGGER.warn("Problem pushing to Vim.", excep); couldNotCall = true; @@ -96,10 +107,12 @@ public void pushEntries(BibDatabaseContext database, List entries, Str @Override public void onOperationCompleted() { if (couldNotPush) { - dialogService.showErrorDialogAndWait(Localization.lang("Error pushing entries"), + dialogService.showErrorDialogAndWait( + Localization.lang("Error pushing entries"), Localization.lang("Could not push to a running Vim server.")); } else if (couldNotCall) { - dialogService.showErrorDialogAndWait(Localization.lang("Error pushing entries"), + dialogService.showErrorDialogAndWait( + Localization.lang("Error pushing entries"), Localization.lang("Could not run the 'vim' program.")); } else { super.onOperationCompleted(); diff --git a/src/main/java/org/jabref/gui/push/PushToVimSettings.java b/src/main/java/org/jabref/gui/push/PushToVimSettings.java index 724fae85eae9..1ad8fe7d3e82 100644 --- a/src/main/java/org/jabref/gui/push/PushToVimSettings.java +++ b/src/main/java/org/jabref/gui/push/PushToVimSettings.java @@ -11,10 +11,11 @@ public class PushToVimSettings extends PushToApplicationSettings { private final TextField vimServer = new TextField(); - public PushToVimSettings(PushToApplication application, - DialogService dialogService, - FilePreferences filePreferences, - PushToApplicationPreferences preferences) { + public PushToVimSettings( + PushToApplication application, + DialogService dialogService, + FilePreferences filePreferences, + PushToApplicationPreferences preferences) { super(application, dialogService, filePreferences, preferences); settingsPane.add(new Label(Localization.lang("Vim server name") + ":"), 0, 1); diff --git a/src/main/java/org/jabref/gui/push/PushToWinEdt.java b/src/main/java/org/jabref/gui/push/PushToWinEdt.java index 7545a75244ca..da3b686f560d 100644 --- a/src/main/java/org/jabref/gui/push/PushToWinEdt.java +++ b/src/main/java/org/jabref/gui/push/PushToWinEdt.java @@ -25,7 +25,13 @@ public JabRefIcon getApplicationIcon() { @Override protected String[] getCommandLine(String keyString) { - return new String[] {commandPath, - "\"[InsText('" + getCitePrefix() + keyString.replace("'", "''") + getCiteSuffix() + "');]\""}; + return new String[] { + commandPath, + "\"[InsText('" + + getCitePrefix() + + keyString.replace("'", "''") + + getCiteSuffix() + + "');]\"" + }; } } diff --git a/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java b/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java index e42bcc2e3580..2387d5d83a6f 100644 --- a/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java +++ b/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java @@ -1,20 +1,19 @@ package org.jabref.gui.remote; -import java.util.Arrays; - import javafx.application.Platform; +import org.apache.commons.cli.ParseException; import org.jabref.cli.ArgumentProcessor; import org.jabref.gui.frame.UiMessageHandler; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.logic.remote.server.RemoteMessageHandler; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; - -import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; + public class CLIMessageHandler implements RemoteMessageHandler { private static final Logger LOGGER = LoggerFactory.getLogger(CLIMessageHandler.class); @@ -23,10 +22,11 @@ public class CLIMessageHandler implements RemoteMessageHandler { private final BibEntryTypesManager entryTypesManager; private final UiMessageHandler uiMessageHandler; - public CLIMessageHandler(UiMessageHandler uiMessageHandler, - GuiPreferences preferences, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager) { + public CLIMessageHandler( + UiMessageHandler uiMessageHandler, + GuiPreferences preferences, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) { this.uiMessageHandler = uiMessageHandler; this.preferences = preferences; this.fileUpdateMonitor = fileUpdateMonitor; @@ -37,15 +37,17 @@ public CLIMessageHandler(UiMessageHandler uiMessageHandler, public void handleCommandLineArguments(String[] message) { try { LOGGER.info("Processing message {}", Arrays.stream(message).toList()); - ArgumentProcessor argumentProcessor = new ArgumentProcessor( - message, - ArgumentProcessor.Mode.REMOTE_START, - preferences, - preferences, - fileUpdateMonitor, - entryTypesManager); + ArgumentProcessor argumentProcessor = + new ArgumentProcessor( + message, + ArgumentProcessor.Mode.REMOTE_START, + preferences, + preferences, + fileUpdateMonitor, + entryTypesManager); argumentProcessor.processArguments(); - Platform.runLater(() -> uiMessageHandler.handleUiCommands(argumentProcessor.getUiCommands())); + Platform.runLater( + () -> uiMessageHandler.handleUiCommands(argumentProcessor.getUiCommands())); } catch (ParseException e) { LOGGER.error("Error when parsing CLI args", e); } diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index cc87b5cfd179..a94cc9528f33 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -1,15 +1,10 @@ package org.jabref.gui.search; -import java.lang.reflect.Field; -import java.time.Duration; -import java.util.EnumSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; -import javax.swing.undo.UndoManager; +import com.tobiasdiez.easybind.EasyBind; + +import impl.org.controlsfx.skin.AutoCompletePopup; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -41,6 +36,8 @@ import javafx.scene.text.Text; import javafx.scene.text.TextFlow; +import org.controlsfx.control.textfield.AutoCompletionBinding; +import org.controlsfx.control.textfield.CustomTextField; import org.jabref.architecture.AllowedToUseClassGetResource; import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; @@ -66,17 +63,21 @@ import org.jabref.model.entry.Author; import org.jabref.model.search.SearchFlags; import org.jabref.model.search.SearchQuery; - -import com.tobiasdiez.easybind.EasyBind; -import impl.org.controlsfx.skin.AutoCompletePopup; -import org.controlsfx.control.textfield.AutoCompletionBinding; -import org.controlsfx.control.textfield.CustomTextField; import org.reactfx.util.FxTimer; import org.reactfx.util.Timer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.lang.reflect.Field; +import java.time.Duration; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import javax.swing.undo.UndoManager; public class GlobalSearchBar extends HBox { @@ -102,12 +103,13 @@ public class GlobalSearchBar extends HBox { 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,53 +126,71 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, searchField.disableProperty().bind(needsDatabase(stateManager).not()); 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) -> { - if (illegal) { - searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, true); - return Localization.lang("Search failed: illegal search expression"); - } else if (searchQuery.isEmpty()) { - searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, false); - return ""; - } else if (matched.intValue() == 0) { - searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, false); - return Localization.lang("No results found."); - } else { - searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, false); - return Localization.lang("Found %0 results.", String.valueOf(matched)); - } - } - )); + currentResults + .textProperty() + .bind( + EasyBind.combine( + stateManager.activeSearchQuery(searchType), + stateManager.searchResultSize(searchType), + illegalSearch, + (searchQuery, matched, illegal) -> { + if (illegal) { + searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, true); + return Localization.lang( + "Search failed: illegal search expression"); + } else if (searchQuery.isEmpty()) { + searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, false); + return ""; + } else if (matched.intValue() == 0) { + searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, false); + return Localization.lang("No results found."); + } else { + searchField.pseudoClassStateChanged(ILLEGAL_SEARCH, false); + return Localization.lang( + "Found %0 results.", String.valueOf(matched)); + } + })); searchField.setTooltip(searchFieldTooltip); searchFieldTooltip.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); searchFieldTooltip.setMaxHeight(10); updateHintVisibility(); - searchField.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (keyBindingRepository.matches(event, KeyBinding.CLEAR_SEARCH)) { - searchField.clear(); - if (searchType == SearchType.NORMAL_SEARCH) { - LibraryTab currentLibraryTab = tabContainer.getCurrentLibraryTab(); - if (currentLibraryTab != null) { - currentLibraryTab.getMainTable().requestFocus(); + searchField.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + if (keyBindingRepository.matches(event, KeyBinding.CLEAR_SEARCH)) { + searchField.clear(); + if (searchType == SearchType.NORMAL_SEARCH) { + LibraryTab currentLibraryTab = tabContainer.getCurrentLibraryTab(); + if (currentLibraryTab != null) { + currentLibraryTab.getMainTable().requestFocus(); + } + } + event.consume(); } - } - event.consume(); - } - }); + }); ClipBoardManager.addX11Support(searchField); searchField.setContextMenu(SearchFieldRightClickMenu.create(stateManager, searchField)); - stateManager.getWholeSearchHistory().addListener((ListChangeListener.Change change) -> { - searchField.getContextMenu().getItems().removeLast(); - searchField.getContextMenu().getItems().add(SearchFieldRightClickMenu.createSearchFromHistorySubMenu(stateManager, searchField)); - }); + stateManager + .getWholeSearchHistory() + .addListener( + (ListChangeListener.Change change) -> { + searchField.getContextMenu().getItems().removeLast(); + searchField + .getContextMenu() + .getItems() + .add( + SearchFieldRightClickMenu + .createSearchFromHistorySubMenu( + stateManager, searchField)); + }); fulltextButton = IconTheme.JabRefIcons.FULLTEXT.asToggleButton(); openGlobalSearchButton = IconTheme.JabRefIcons.OPEN_GLOBAL_SEARCH.asButton(); @@ -179,11 +199,13 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, initSearchModifierButtons(); - BooleanBinding focusedOrActive = searchField.focusedProperty() - .or(fulltextButton.focusedProperty()) - .or(keepSearchString.focusedProperty()) - .or(filterModeButton.focusedProperty()) - .or(searchField.textProperty().isNotEmpty()); + BooleanBinding focusedOrActive = + searchField + .focusedProperty() + .or(fulltextButton.focusedProperty()) + .or(keepSearchString.focusedProperty()) + .or(filterModeButton.focusedProperty()) + .or(searchField.textProperty().isNotEmpty()); fulltextButton.visibleProperty().unbind(); fulltextButton.visibleProperty().bind(focusedOrActive); @@ -194,7 +216,8 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, StackPane modifierButtons; if (searchType == SearchType.NORMAL_SEARCH) { - modifierButtons = new StackPane(new HBox(fulltextButton, keepSearchString, filterModeButton)); + modifierButtons = + new StackPane(new HBox(fulltextButton, keepSearchString, filterModeButton)); } else { modifierButtons = new StackPane(new HBox(fulltextButton)); } @@ -221,7 +244,12 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, // Async update searchTask.restart(); }, - query -> setSearchTerm(query.orElseGet(() -> new SearchQuery("", EnumSet.noneOf(SearchFlags.class))))); + query -> + setSearchTerm( + query.orElseGet( + () -> + new SearchQuery( + "", EnumSet.noneOf(SearchFlags.class))))); /* * The listener tracks a change on the focus property value. @@ -229,68 +257,98 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, * 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) - if (oldValue && !(newValue || searchField.getText().isBlank())) { - this.stateManager.addSearchHistory(searchField.textProperty().get()); - } - }); + searchField + .focusedProperty() + .addListener( + (obs, oldValue, newValue) -> { + // 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()); + } + }); } private void initSearchModifierButtons() { fulltextButton.setTooltip(new Tooltip(Localization.lang("Fulltext search"))); initSearchModifierButton(fulltextButton); - EasyBind.subscribe(filePreferences.fulltextIndexLinkedFilesProperty(), enabled -> { - if (!enabled) { - fulltextButton.setSelected(false); - } 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")); - - LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); - if (libraryTab != null && enableFulltextSearch) { - filePreferences.setFulltextIndexLinkedFiles(true); - searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, true); - } - if (!enableFulltextSearch) { - fulltextButton.setSelected(false); - searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, false); - } - } else { - searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, newVal); - } - searchField.requestFocus(); - updateSearchQuery(); - }); + EasyBind.subscribe( + filePreferences.fulltextIndexLinkedFilesProperty(), + enabled -> { + if (!enabled) { + fulltextButton.setSelected(false); + } 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")); + + LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); + if (libraryTab != null && enableFulltextSearch) { + filePreferences.setFulltextIndexLinkedFiles(true); + searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, true); + } + if (!enableFulltextSearch) { + fulltextButton.setSelected(false); + searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, false); + } + } else { + searchPreferences.setSearchFlag(SearchFlags.FULLTEXT, newVal); + } + searchField.requestFocus(); + updateSearchQuery(); + }); keepSearchString.setSelected(searchPreferences.shouldKeepSearchString()); - keepSearchString.setTooltip(new Tooltip(Localization.lang("Keep search string across libraries"))); + keepSearchString.setTooltip( + new Tooltip(Localization.lang("Keep search string across libraries"))); initSearchModifierButton(keepSearchString); - keepSearchString.selectedProperty().addListener((obs, oldVal, newVal) -> { - searchPreferences.setKeepSearchString(newVal); - searchField.requestFocus(); - }); - - filterModeButton.setSelected(searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FILTER); + keepSearchString + .selectedProperty() + .addListener( + (obs, oldVal, newVal) -> { + searchPreferences.setKeepSearchString(newVal); + searchField.requestFocus(); + }); + + filterModeButton.setSelected( + searchPreferences.getSearchDisplayMode() == SearchDisplayMode.FILTER); filterModeButton.setTooltip(new Tooltip(Localization.lang("Filter search results"))); initSearchModifierButton(filterModeButton); - filterModeButton.setOnAction(event -> { - searchPreferences.setSearchDisplayMode(filterModeButton.isSelected() ? SearchDisplayMode.FILTER : SearchDisplayMode.FLOAT); - searchField.requestFocus(); - }); + filterModeButton.setOnAction( + event -> { + searchPreferences.setSearchDisplayMode( + filterModeButton.isSelected() + ? SearchDisplayMode.FILTER + : SearchDisplayMode.FLOAT); + searchField.requestFocus(); + }); openGlobalSearchButton.disableProperty().bindBidirectional(globalSearchActive); - openGlobalSearchButton.setTooltip(new Tooltip(Localization.lang("Search across libraries in a new window"))); + openGlobalSearchButton.setTooltip( + new Tooltip(Localization.lang("Search across libraries in a new window"))); initSearchModifierButton(openGlobalSearchButton); openGlobalSearchButton.setOnAction(evt -> openGlobalSearchDialog()); - searchPreferences.getObservableSearchFlags().addListener((SetChangeListener.Change change) -> fulltextButton.setSelected(searchPreferences.isFulltext())); + searchPreferences + .getObservableSearchFlags() + .addListener( + (SetChangeListener.Change change) -> + fulltextButton.setSelected(searchPreferences.isFulltext())); } public void openGlobalSearchDialog() { @@ -301,8 +359,14 @@ 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); @@ -341,7 +405,8 @@ public void updateSearchQuery() { return; } - SearchQuery searchQuery = new SearchQuery(this.searchField.getText(), searchPreferences.getSearchFlags()); + SearchQuery searchQuery = + new SearchQuery(this.searchField.getText(), searchPreferences.getSearchFlags()); if (!searchQuery.isValid()) { illegalSearch.set(true); return; @@ -363,10 +428,13 @@ private boolean validRegex() { public void setAutoCompleter(SuggestionProvider searchCompleter) { if (preferences.getAutoCompletePreferences().shouldAutoComplete()) { - AutoCompletionTextInputBinding autoComplete = AutoCompletionTextInputBinding.autoComplete(searchField, - searchCompleter::provideSuggestions, - new PersonNameStringConverter(false, false, AutoCompleteFirstNameMode.BOTH), - new AppendPersonNamesStrategy()); + AutoCompletionTextInputBinding autoComplete = + AutoCompletionTextInputBinding.autoComplete( + searchField, + searchCompleter::provideSuggestions, + new PersonNameStringConverter( + false, false, AutoCompleteFirstNameMode.BOTH), + new AppendPersonNamesStrategy()); AutoCompletePopup popup = getPopup(autoComplete); popup.setSkin(new SearchPopupSkin<>(popup)); } @@ -379,7 +447,8 @@ public void setAutoCompleter(SuggestionProvider searchCompleter) { private AutoCompletePopup getPopup(AutoCompletionBinding autoCompletionBinding) { try { // TODO: reflective access, should be removed - Field privatePopup = AutoCompletionBinding.class.getDeclaredField("autoCompletionPopup"); + Field privatePopup = + AutoCompletionBinding.class.getDeclaredField("autoCompletionPopup"); privatePopup.setAccessible(true); return (AutoCompletePopup) privatePopup.get(autoCompletionBinding); } catch (IllegalAccessException | NoSuchFieldException e) { @@ -390,8 +459,11 @@ private AutoCompletePopup getPopup(AutoCompletionBinding autoCompletio public void updateHintVisibility() { 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"); - List genericDescriptionTexts = TooltipTextUtil.createTextsFromHtml(genericDescription); + 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(); emptyHintTooltip.getChildren().setAll(genericDescriptionTexts); @@ -418,9 +490,23 @@ 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.setCellFactory(TextFieldListCell.forListView(control.getConverter())); + 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()); this.suggestionList.minWidthProperty().bind(control.minWidthProperty()); @@ -432,26 +518,30 @@ public SearchPopupSkin(AutoCompletePopup control) { } private void registerEventListener() { - this.suggestionList.setOnMouseClicked(me -> { - if (me.getButton() == MouseButton.PRIMARY) { - this.onSuggestionChosen(this.suggestionList.getSelectionModel().getSelectedItem()); - } - }); - this.suggestionList.setOnKeyPressed(ke -> { - switch (ke.getCode()) { - case TAB: - case ENTER: - this.onSuggestionChosen(this.suggestionList.getSelectionModel().getSelectedItem()); - break; - case ESCAPE: - if (this.control.isHideOnEscape()) { - this.control.hide(); + this.suggestionList.setOnMouseClicked( + me -> { + if (me.getButton() == MouseButton.PRIMARY) { + this.onSuggestionChosen( + this.suggestionList.getSelectionModel().getSelectedItem()); + } + }); + this.suggestionList.setOnKeyPressed( + ke -> { + switch (ke.getCode()) { + case TAB: + case ENTER: + this.onSuggestionChosen( + this.suggestionList.getSelectionModel().getSelectedItem()); + break; + case ESCAPE: + if (this.control.isHideOnEscape()) { + this.control.hide(); + } + break; + default: + break; } - break; - default: - break; - } - }); + }); } private void onSuggestionChosen(T suggestion) { diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java index 87d72be77744..7e1584aa69e7 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialog.java @@ -1,6 +1,10 @@ package org.jabref.gui.search; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.SplitPane; @@ -23,10 +27,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.TaskExecutor; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import com.tobiasdiez.easybind.Subscription; -import jakarta.inject.Inject; +import javax.swing.undo.UndoManager; public class GlobalSearchResultDialog extends BaseDialog { @@ -37,8 +38,10 @@ public class GlobalSearchResultDialog extends BaseDialog { 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 StateManager stateManager; @@ -46,82 +49,139 @@ public class GlobalSearchResultDialog extends BaseDialog { @Inject private ThemeManager themeManager; @Inject private TaskExecutor taskExecutor; - public GlobalSearchResultDialog(UndoManager undoManager, LibraryTabContainer libraryTabContainer) { + 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()); - - GlobalSearchBar searchBar = new GlobalSearchBar(libraryTabContainer, stateManager, preferences, undoManager, dialogService, SearchType.GLOBAL_SEARCH); + GlobalSearchResultDialogViewModel viewModel = + new GlobalSearchResultDialogViewModel(preferences.getSearchPreferences()); + + 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(viewModel.getSearchDatabaseContext(), dialogService, preferences, themeManager, taskExecutor, stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH)); + PreviewViewer previewViewer = + new PreviewViewer( + viewModel.getSearchDatabaseContext(), + dialogService, + preferences, + themeManager, + taskExecutor, + stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH)); previewViewer.setLayout(preferences.getPreviewPreferences().getSelectedPreviewLayout()); - 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 { - previewViewer.setEntry(oldValue.getEntry()); - } - }); + resultsTable + .getSelectionModel() + .selectedItemProperty() + .addListener( + (obs, oldValue, newValue) -> { + if (newValue != null) { + previewViewer.setEntry(newValue.getEntry()); + } else { + previewViewer.setEntry(oldValue.getEntry()); + } + }); Stage stage = (Stage) getDialogPane().getScene().getWindow(); - resultsTable.setOnMouseClicked(event -> { - if (event.getClickCount() == 2) { - BibEntryTableViewModel selectedEntry = resultsTable.getSelectionModel().getSelectedItem(); - 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()); - stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(selectedEntry.getEntry())); - stage.close(); - } - }); + resultsTable.setOnMouseClicked( + event -> { + if (event.getClickCount() == 2) { + BibEntryTableViewModel selectedEntry = + resultsTable.getSelectionModel().getSelectedItem(); + 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()); + stateManager + .activeTabProperty() + .get() + .ifPresent(tab -> tab.clearAndSelect(selectedEntry.getEntry())); + stage.close(); + } + }); container.getItems().addAll(resultsTable, previewViewer); keepOnTop.selectedProperty().bindBidirectional(viewModel.keepOnTop()); - keepOnTopSubscription = EasyBind.subscribe(viewModel.keepOnTop(), value -> { - stage.setAlwaysOnTop(value); - keepOnTop.setGraphic(value - ? IconTheme.JabRefIcons.KEEP_ON_TOP.getGraphicNode() - : IconTheme.JabRefIcons.KEEP_ON_TOP_OFF.getGraphicNode()); - }); - - stage.setOnShown(event -> { - stage.setHeight(preferences.getSearchPreferences().getSearchWindowHeight()); - stage.setWidth(preferences.getSearchPreferences().getSearchWindowWidth()); - container.setDividerPositions(preferences.getSearchPreferences().getSearchWindowDividerPosition()); - searchBar.requestFocus(); - }); - - stage.setOnHidden(event -> { - preferences.getSearchPreferences().setSearchWindowHeight(getHeight()); - preferences.getSearchPreferences().setSearchWindowWidth(getWidth()); - preferences.getSearchPreferences().setSearchWindowDividerPosition(container.getDividers().getFirst().getPosition()); - }); + keepOnTopSubscription = + EasyBind.subscribe( + viewModel.keepOnTop(), + value -> { + stage.setAlwaysOnTop(value); + keepOnTop.setGraphic( + value + ? IconTheme.JabRefIcons.KEEP_ON_TOP.getGraphicNode() + : IconTheme.JabRefIcons.KEEP_ON_TOP_OFF + .getGraphicNode()); + }); + + stage.setOnShown( + event -> { + stage.setHeight(preferences.getSearchPreferences().getSearchWindowHeight()); + stage.setWidth(preferences.getSearchPreferences().getSearchWindowWidth()); + container.setDividerPositions( + preferences.getSearchPreferences().getSearchWindowDividerPosition()); + searchBar.requestFocus(); + }); + + stage.setOnHidden( + event -> { + preferences.getSearchPreferences().setSearchWindowHeight(getHeight()); + preferences.getSearchPreferences().setSearchWindowWidth(getWidth()); + preferences + .getSearchPreferences() + .setSearchWindowDividerPosition( + container.getDividers().getFirst().getPosition()); + }); } } diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java index 7c267c2396c6..ced418cf7a69 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchResultDialogViewModel.java @@ -1,13 +1,13 @@ package org.jabref.gui.search; +import com.tobiasdiez.easybind.EasyBind; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import org.jabref.logic.search.SearchPreferences; import org.jabref.model.database.BibDatabaseContext; -import com.tobiasdiez.easybind.EasyBind; - public class GlobalSearchResultDialogViewModel { private final BibDatabaseContext searchDatabaseContext = new BibDatabaseContext(); private final BooleanProperty keepOnTop = new SimpleBooleanProperty(); diff --git a/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java b/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java index c623edb60e4b..73c621933985 100644 --- a/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java +++ b/src/main/java/org/jabref/gui/search/RebuildFulltextSearchIndexAction.java @@ -1,6 +1,6 @@ package org.jabref.gui.search; -import java.util.function.Supplier; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; @@ -9,7 +9,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.logic.preferences.CliPreferences; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.util.function.Supplier; public class RebuildFulltextSearchIndexAction extends SimpleCommand { @@ -18,14 +18,17 @@ public class RebuildFulltextSearchIndexAction extends SimpleCommand { 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 @@ -39,9 +42,10 @@ public void init() { return; } - boolean confirm = dialogService.showConfirmationDialogAndWait( - Localization.lang("Rebuild fulltext search index"), - Localization.lang("Rebuild fulltext search index for current library?")); + boolean confirm = + dialogService.showConfirmationDialogAndWait( + Localization.lang("Rebuild fulltext search index"), + Localization.lang("Rebuild fulltext search index for current library?")); if (!confirm) { shouldContinue = false; return; diff --git a/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java b/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java index 1d2a64ad0b5c..0c1952290b56 100644 --- a/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java +++ b/src/main/java/org/jabref/gui/search/SearchFieldRightClickMenu.java @@ -1,12 +1,11 @@ package org.jabref.gui.search; -import java.util.List; - import javafx.scene.control.ContextMenu; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; +import org.controlsfx.control.textfield.CustomTextField; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.SimpleCommand; @@ -14,29 +13,47 @@ import org.jabref.gui.fieldeditors.contextmenu.EditorContextAction; import org.jabref.logic.l10n.Localization; -import org.controlsfx.control.textfield.CustomTextField; +import java.util.List; 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; } - public static Menu createSearchFromHistorySubMenu(StateManager stateManager, CustomTextField searchField) { + public static Menu createSearchFromHistorySubMenu( + StateManager stateManager, CustomTextField searchField) { ActionFactory factory = new ActionFactory(); - Menu searchFromHistorySubMenu = factory.createMenu(() -> Localization.lang("Search from history...")); + Menu searchFromHistorySubMenu = + factory.createMenu(() -> Localization.lang("Search from history...")); final int numberOfLastQueries = 10; List searchHistory = stateManager.getLastSearchHistory(numberOfLastQueries); @@ -45,20 +62,26 @@ public static Menu createSearchFromHistorySubMenu(StateManager stateManager, Cus searchFromHistorySubMenu.getItems().add(item); } else { for (String query : searchHistory) { - MenuItem item = factory.createMenuItem(() -> query, new SimpleCommand() { - @Override - public void execute() { - searchField.setText(query); - } - }); + MenuItem item = + factory.createMenuItem( + () -> query, + new SimpleCommand() { + @Override + public void execute() { + searchField.setText(query); + } + }); searchFromHistorySubMenu.getItems().add(item); } - MenuItem clear = factory.createMenuItem(() -> Localization.lang("Clear history"), new SimpleCommand() { - @Override - public void execute() { - stateManager.clearSearchHistory(); - } - }); + MenuItem clear = + factory.createMenuItem( + () -> Localization.lang("Clear history"), + new SimpleCommand() { + @Override + public void execute() { + stateManager.clearSearchHistory(); + } + }); searchFromHistorySubMenu.getItems().addAll(new SeparatorMenuItem(), clear); } return searchFromHistorySubMenu; diff --git a/src/main/java/org/jabref/gui/search/SearchResultsTable.java b/src/main/java/org/jabref/gui/search/SearchResultsTable.java index 76ebb2cff264..04e54e324827 100644 --- a/src/main/java/org/jabref/gui/search/SearchResultsTable.java +++ b/src/main/java/org/jabref/gui/search/SearchResultsTable.java @@ -1,9 +1,5 @@ package org.jabref.gui.search; -import java.util.List; - -import javax.swing.undo.UndoManager; - import javafx.scene.control.SelectionMode; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -22,30 +18,37 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; +import java.util.List; + +import javax.swing.undo.UndoManager; + @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 +56,16 @@ 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()); @@ -70,9 +77,9 @@ public SearchResultsTable(SearchResultsTableDataModel model, model.getEntriesFilteredAndSorted().comparatorProperty().bind(this.comparatorProperty()); // Store visual state - new PersistenceVisualStateTable(this, preferences.getSearchDialogColumnPreferences()).addListeners(); + new PersistenceVisualStateTable(this, preferences.getSearchDialogColumnPreferences()) + .addListeners(); database.getDatabase().registerListener(this); } } - diff --git a/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java b/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java index b6f7503ccbbe..2b8069edb56f 100644 --- a/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java +++ b/src/main/java/org/jabref/gui/search/SearchResultsTableDataModel.java @@ -1,6 +1,6 @@ package org.jabref.gui.search; -import java.util.Optional; +import com.tobiasdiez.easybind.EasyBind; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; @@ -24,65 +24,100 @@ import org.jabref.model.search.SearchQuery; import org.jabref.model.search.SearchResults; -import com.tobiasdiez.easybind.EasyBind; +import java.util.Optional; public class SearchResultsTableDataModel { - private final ObservableList entriesViewModel = FXCollections.observableArrayList(); + 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)); - stateManager.searchResultSize(SearchType.GLOBAL_SEARCH).bind(Bindings.size(entriesFiltered)); + EasyBind.listen( + stateManager.activeSearchQuery(SearchType.GLOBAL_SEARCH), + (observable, oldValue, newValue) -> updateSearchMatches(newValue)); + stateManager + .searchResultSize(SearchType.GLOBAL_SEARCH) + .bind(Bindings.size(entriesFiltered)); } 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); } } private void updateSearchMatches(Optional query) { - BackgroundTask.wrap(() -> { - if (query.isPresent()) { - SearchResults searchResults = new SearchResults(); - for (BibDatabaseContext context : stateManager.getOpenDatabases()) { - stateManager.getLuceneManager(context).ifPresent(luceneManager -> { - searchResults.mergeSearchResults(luceneManager.search(query.get())); - }); - } - for (BibEntryTableViewModel entry : entriesViewModel) { - entry.searchScoreProperty().set(searchResults.getSearchScoreForEntry(entry.getEntry())); - entry.hasFullTextResultsProperty().set(searchResults.hasFulltextResults(entry.getEntry())); - entry.isVisibleBySearch().set(entry.searchScoreProperty().get() > 0); - } - } else { - for (BibEntryTableViewModel entry : entriesViewModel) { - entry.searchScoreProperty().set(0); - entry.hasFullTextResultsProperty().set(false); - entry.isVisibleBySearch().set(true); - } - } - }).onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)).executeWith(taskExecutor); + BackgroundTask.wrap( + () -> { + if (query.isPresent()) { + SearchResults searchResults = new SearchResults(); + for (BibDatabaseContext context : stateManager.getOpenDatabases()) { + stateManager + .getLuceneManager(context) + .ifPresent( + luceneManager -> { + searchResults.mergeSearchResults( + luceneManager.search(query.get())); + }); + } + for (BibEntryTableViewModel entry : entriesViewModel) { + entry.searchScoreProperty() + .set( + searchResults.getSearchScoreForEntry( + entry.getEntry())); + entry.hasFullTextResultsProperty() + .set( + searchResults.hasFulltextResults( + entry.getEntry())); + entry.isVisibleBySearch() + .set(entry.searchScoreProperty().get() > 0); + } + } else { + for (BibEntryTableViewModel entry : entriesViewModel) { + entry.searchScoreProperty().set(0); + entry.hasFullTextResultsProperty().set(false); + entry.isVisibleBySearch().set(true); + } + } + }) + .onSuccess(result -> FilteredListProxy.refilterListReflection(entriesFiltered)) + .executeWith(taskExecutor); } public SortedList getEntriesFilteredAndSorted() { diff --git a/src/main/java/org/jabref/gui/search/SearchTextField.java b/src/main/java/org/jabref/gui/search/SearchTextField.java index 4cb2b060ba62..6e021f3e41e7 100644 --- a/src/main/java/org/jabref/gui/search/SearchTextField.java +++ b/src/main/java/org/jabref/gui/search/SearchTextField.java @@ -3,14 +3,13 @@ import javafx.scene.Node; import javafx.scene.input.KeyEvent; +import org.controlsfx.control.textfield.CustomTextField; +import org.controlsfx.control.textfield.TextFields; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.logic.l10n.Localization; -import org.controlsfx.control.textfield.CustomTextField; -import org.controlsfx.control.textfield.TextFields; - public class SearchTextField { public static CustomTextField create(KeyBindingRepository keyBindingRepository) { @@ -23,16 +22,20 @@ public static CustomTextField create(KeyBindingRepository keyBindingRepository) graphicNode.getStyleClass().add("search-field-icon"); textField.setLeft(graphicNode); - textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> { - // 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 - - if (keyBindingRepository.matches(event, KeyBinding.CLEAR_SEARCH)) { - textField.clear(); - event.consume(); - } - }); + textField.addEventFilter( + KeyEvent.KEY_PRESSED, + event -> { + // 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 + + if (keyBindingRepository.matches(event, KeyBinding.CLEAR_SEARCH)) { + textField.clear(); + event.consume(); + } + }); return textField; } diff --git a/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java b/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java index 16b11a60caec..07d0a6d9a0c6 100644 --- a/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java +++ b/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseCommand.java @@ -12,7 +12,8 @@ public class ConnectToSharedDatabaseCommand extends SimpleCommand { private final LibraryTabContainer tabContainer; private final DialogService dialogService; - public ConnectToSharedDatabaseCommand(LibraryTabContainer tabContainer, DialogService dialogService) { + public ConnectToSharedDatabaseCommand( + LibraryTabContainer tabContainer, DialogService dialogService) { this.tabContainer = tabContainer; this.dialogService = dialogService; } diff --git a/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java b/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java index d19090cf367a..7c34497c94e0 100644 --- a/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java +++ b/src/main/java/org/jabref/gui/shared/PullChangesFromSharedAction.java @@ -12,13 +12,19 @@ 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() { - stateManager.getActiveDatabase().ifPresent(databaseContext -> { - DatabaseSynchronizer dbmsSynchronizer = databaseContext.getDBMSSynchronizer(); - dbmsSynchronizer.pullChanges(); - }); + stateManager + .getActiveDatabase() + .ifPresent( + databaseContext -> { + DatabaseSynchronizer dbmsSynchronizer = + databaseContext.getDBMSSynchronizer(); + dbmsSynchronizer.pullChanges(); + }); } } diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java index ed30a868a2bd..c05595a89d7d 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogView.java @@ -1,6 +1,11 @@ package org.jabref.gui.shared; -import javax.swing.undo.UndoManager; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; import javafx.application.Platform; import javafx.event.ActionEvent; @@ -27,10 +32,7 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; +import javax.swing.undo.UndoManager; /** * This offers the user to connect to a remove SQL database. @@ -74,15 +76,21 @@ 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 +106,18 @@ 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); @@ -139,18 +148,25 @@ private void initialize() { rememberPassword.selectedProperty().bindBidirectional(viewModel.rememberPasswordProperty()); // 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(useSSL.selectedProperty(), selected -> - visualizer.initVisualization(viewModel.keystoreValidation(), fileKeystore, true)); - }); + 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( + useSSL.selectedProperty(), + selected -> + visualizer.initVisualization( + viewModel.keystoreValidation(), fileKeystore, true)); + }); } @FXML diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java index e4c9d13fc049..856f96fe5f83 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java @@ -1,15 +1,13 @@ package org.jabref.gui.shared; -import java.io.UnsupportedEncodingException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.GeneralSecurityException; -import java.sql.SQLException; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; +import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; -import javax.swing.undo.UndoManager; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; @@ -48,22 +46,27 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; - -import com.airhacks.afterburner.injection.Injector; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.CompositeValidator; -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.sql.SQLException; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import javax.swing.undo.UndoManager; + public class SharedDatabaseLoginDialogViewModel extends AbstractViewModel { - private static final Logger LOGGER = LoggerFactory.getLogger(SharedDatabaseLoginDialogViewModel.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(SharedDatabaseLoginDialogViewModel.class); - private final ObjectProperty selectedDBMSType = new SimpleObjectProperty<>(DBMSType.values()[0]); + private final ObjectProperty selectedDBMSType = + new SimpleObjectProperty<>(DBMSType.values()[0]); private final StringProperty database = new SimpleStringProperty(""); private final StringProperty host = new SimpleStringProperty(""); @@ -85,7 +88,8 @@ public class SharedDatabaseLoginDialogViewModel extends AbstractViewModel { private final DialogService dialogService; private final GuiPreferences preferences; private final AiService aiService; - private final SharedDatabasePreferences sharedDatabasePreferences = new SharedDatabasePreferences(); + private final SharedDatabasePreferences sharedDatabasePreferences = + new SharedDatabasePreferences(); private final StateManager stateManager; private final BibEntryTypesManager entryTypesManager; private final FileUpdateMonitor fileUpdateMonitor; @@ -101,16 +105,17 @@ public class SharedDatabaseLoginDialogViewModel extends AbstractViewModel { 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; @@ -122,18 +127,58 @@ public SharedDatabaseLoginDialogViewModel(LibraryTabContainer tabContainer, this.clipBoardManager = clipBoardManager; this.taskExecutor = taskExecutor; - EasyBind.subscribe(selectedDBMSType, selected -> port.setValue(Integer.toString(selected.getDefaultPort()))); + EasyBind.subscribe( + selectedDBMSType, + selected -> port.setValue(Integer.toString(selected.getDefaultPort()))); Predicate notEmpty = input -> (input != null) && !input.trim().isEmpty(); 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); @@ -142,21 +187,24 @@ 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(); + 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(); setupKeyStore(); return openSharedDatabase(connectionProperties); @@ -170,8 +218,10 @@ private void setupKeyStore() { private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties) { if (isSharedDatabaseAlreadyPresent(connectionProperties)) { - dialogService.showWarningDialogAndWait(Localization.lang("Shared database connection"), - Localization.lang("You are already connected to a database using entered connection details.")); + dialogService.showWarningDialogAndWait( + Localization.lang("Shared database connection"), + Localization.lang( + "You are already connected to a database using entered connection details.")); return true; } @@ -179,10 +229,14 @@ 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"), - Localization.lang("'%0' exists. Overwrite file?", localFilePath.getFileName().toString()), - Localization.lang("Overwrite file"), - Localization.lang("Cancel")); + boolean overwriteFilePressed = + dialogService.showConfirmationDialogAndWait( + Localization.lang("Existing file"), + Localization.lang( + "'%0' exists. Overwrite file?", + localFilePath.getFileName().toString()), + Localization.lang("Overwrite file"), + Localization.lang("Cancel")); if (!overwriteFilePressed) { return true; } @@ -192,28 +246,29 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties loading.set(true); try { - SharedDatabaseUIManager manager = new SharedDatabaseUIManager( - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipBoardManager, - taskExecutor); + 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) - ).saveAs(Path.of(folder.getValue())); + libraryTab, + dialogService, + preferences, + Injector.instantiateModelOrService(BibEntryTypesManager.class)) + .saveAs(Path.of(folder.getValue())); } catch (Throwable e) { LOGGER.error("Error while saving the database", e); } @@ -225,17 +280,32 @@ private boolean openSharedDatabase(DBMSConnectionProperties connectionProperties } 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."), - 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(ButtonType.OK)).ifPresent(btn -> openSharedDatabase(connectionProperties)); + 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."), + 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(ButtonType.OK)) + .ifPresent(btn -> openSharedDatabase(connectionProperties)); } loading.set(false); return false; @@ -253,12 +323,14 @@ private void setPreferences() { if (rememberPassword.get()) { try { - sharedDatabasePreferences.setPassword(new Password(password.getValue(), user.getValue()).encrypt()); + sharedDatabasePreferences.setPassword( + new Password(password.getValue(), user.getValue()).encrypt()); } 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 + sharedDatabasePreferences + .clearPassword(); // for the case that the password is already set } sharedDatabasePreferences.setRememberPassword(rememberPassword.get()); @@ -296,7 +368,11 @@ private void applyPreferences() { if (sharedDatabasePassword.isPresent() && sharedDatabaseUser.isPresent()) { try { - password.setValue(new Password(sharedDatabasePassword.get().toCharArray(), sharedDatabaseUser.get()).decrypt()); + 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); } @@ -310,31 +386,39 @@ private void applyPreferences() { private boolean isSharedDatabaseAlreadyPresent(DBMSConnectionProperties connectionProperties) { List libraryTabs = tabContainer.getLibraryTabs(); - return libraryTabs.parallelStream().anyMatch(panel -> { - BibDatabaseContext context = panel.getBibDatabaseContext(); + 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(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .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(); + FileDialogConfiguration fileDialogConfiguration = + new FileDialogConfiguration.Builder() + .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())); } diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index d3f2989b36ec..e4cffc0c97af 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -1,9 +1,6 @@ package org.jabref.gui.shared; -import java.sql.SQLException; -import java.util.Optional; - -import javax.swing.undo.UndoManager; +import com.google.common.eventbus.Subscribe; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.ButtonBar; @@ -41,7 +38,10 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; -import com.google.common.eventbus.Subscribe; +import java.sql.SQLException; +import java.util.Optional; + +import javax.swing.undo.UndoManager; public class SharedDatabaseUIManager { @@ -57,16 +57,17 @@ public class SharedDatabaseUIManager { 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; @@ -83,19 +84,23 @@ public SharedDatabaseUIManager(LibraryTabContainer tabContainer, public void listen(ConnectionLostEvent connectionLostEvent) { ButtonType reconnect = new ButtonType(Localization.lang("Reconnect"), ButtonData.YES); ButtonType workOffline = new ButtonType(Localization.lang("Work offline"), ButtonData.NO); - ButtonType closeLibrary = new ButtonType(Localization.lang("Close library"), ButtonData.CANCEL_CLOSE); - - Optional answer = dialogService.showCustomButtonDialogAndWait(AlertType.WARNING, - Localization.lang("Connection lost"), - Localization.lang("The connection to the server has been terminated."), - reconnect, - workOffline, - closeLibrary); + ButtonType closeLibrary = + new ButtonType(Localization.lang("Close library"), ButtonData.CANCEL_CLOSE); + + Optional answer = + dialogService.showCustomButtonDialogAndWait( + AlertType.WARNING, + Localization.lang("Connection lost"), + 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)); + dialogService.showCustomDialogAndWait( + new SharedDatabaseLoginDialogView(tabContainer)); } else if (answer.get().equals(workOffline)) { connectionLostEvent.getBibDatabaseContext().convertToLocalDatabase(); tabContainer.getLibraryTabs().forEach(tab -> tab.updateTabTitle(tab.isModified())); @@ -113,29 +118,58 @@ public void listen(UpdateRefusedEvent updateRefusedEvent) { BibEntry localBibEntry = updateRefusedEvent.getLocalBibEntry(); BibEntry sharedBibEntry = updateRefusedEvent.getSharedBibEntry(); - 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 BibEntry.") + "\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); + 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 BibEntry.") + + "\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); if (response.isPresent() && response.get().equals(merge)) { - MergeEntriesDialog dialog = new MergeEntriesDialog(localBibEntry, sharedBibEntry, preferences); + MergeEntriesDialog dialog = + new MergeEntriesDialog(localBibEntry, sharedBibEntry, preferences); dialog.setTitle(Localization.lang("Update refused")); - Optional mergedEntry = dialogService.showCustomDialogAndWait(dialog).map(EntriesMergeResult::mergedEntry); - - mergedEntry.ifPresent(mergedBibEntry -> { - mergedBibEntry.getSharedBibEntryData().setSharedID(sharedBibEntry.getSharedBibEntryData().getSharedID()); - mergedBibEntry.getSharedBibEntryData().setVersion(sharedBibEntry.getSharedBibEntryData().getVersion()); - - dbmsSynchronizer.synchronizeSharedEntry(mergedBibEntry); - dbmsSynchronizer.synchronizeLocalDatabase(); - }); + Optional mergedEntry = + dialogService + .showCustomDialogAndWait(dialog) + .map(EntriesMergeResult::mergedEntry); + + mergedEntry.ifPresent( + mergedBibEntry -> { + mergedBibEntry + .getSharedBibEntryData() + .setSharedID(sharedBibEntry.getSharedBibEntryData().getSharedID()); + mergedBibEntry + .getSharedBibEntryData() + .setVersion(sharedBibEntry.getSharedBibEntryData().getVersion()); + + dbmsSynchronizer.synchronizeSharedEntry(mergedBibEntry); + dbmsSynchronizer.synchronizeLocalDatabase(); + }); } } @@ -144,13 +178,20 @@ public void listen(SharedEntriesNotPresentEvent event) { LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); EntryEditor entryEditor = libraryTab.getEntryEditor(); - libraryTab.getUndoManager().addEdit(new UndoableRemoveEntries(libraryTab.getDatabase(), event.getBibEntries())); - - if (entryEditor != null && (event.getBibEntries().contains(entryEditor.getCurrentlyEditedEntry()))) { - 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.") + libraryTab + .getUndoManager() + .addEdit( + new UndoableRemoveEntries(libraryTab.getDatabase(), event.getBibEntries())); + + if (entryEditor != null + && (event.getBibEntries().contains(entryEditor.getCurrentlyEditedEntry()))) { + 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("You can restore the entry using the \"Undo\" operation.")); + + Localization.lang( + "You can restore the entry using the \"Undo\" operation.")); libraryTab.closeBottomPane(); } } @@ -162,52 +203,66 @@ public void listen(SharedEntriesNotPresentEvent event) { * @return BasePanel which also used by {@link SaveDatabaseAction} */ public LibraryTab openNewSharedDatabaseTab(DBMSConnectionProperties dbmsConnectionProperties) - throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException { + throws SQLException, + DatabaseNotSupportedException, + InvalidDBMSConnectionPropertiesException { BibDatabaseContext bibDatabaseContext = getBibDatabaseContextForSharedDatabase(); 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, - preferences, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipBoardManager, - taskExecutor); + dialogService.notify( + Localization.lang( + "Connection to %0 server established.", + dbmsConnectionProperties.getType().toString())); + + LibraryTab libraryTab = + LibraryTab.createLibraryTab( + bibDatabaseContext, + tabContainer, + dialogService, + preferences, + stateManager, + fileUpdateMonitor, + entryTypesManager, + undoManager, + clipBoardManager, + taskExecutor); tabContainer.addTab(libraryTab, true); return libraryTab; } public void openSharedDatabaseFromParserResult(ParserResult parserResult) - throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException, - NotASharedDatabaseException { + throws SQLException, + DatabaseNotSupportedException, + InvalidDBMSConnectionPropertiesException, + NotASharedDatabaseException { - Optional sharedDatabaseIDOptional = parserResult.getDatabase().getSharedDatabaseID(); + Optional sharedDatabaseIDOptional = + parserResult.getDatabase().getSharedDatabaseID(); if (sharedDatabaseIDOptional.isEmpty()) { throw new NotASharedDatabaseException(); } String sharedDatabaseID = sharedDatabaseIDOptional.get(); - DBMSConnectionProperties dbmsConnectionProperties = new DBMSConnectionProperties(new SharedDatabasePreferences(sharedDatabaseID)); + DBMSConnectionProperties dbmsConnectionProperties = + new DBMSConnectionProperties(new SharedDatabasePreferences(sharedDatabaseID)); BibDatabaseContext bibDatabaseContext = getBibDatabaseContextForSharedDatabase(); bibDatabaseContext.getDatabase().setSharedDatabaseID(sharedDatabaseID); - bibDatabaseContext.setDatabasePath(parserResult.getDatabaseContext().getDatabasePath().orElse(null)); + bibDatabaseContext.setDatabasePath( + parserResult.getDatabaseContext().getDatabasePath().orElse(null)); 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); } @@ -215,12 +270,13 @@ 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/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java b/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java index e5f48ae21f4d..467cedf302b5 100644 --- a/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java +++ b/src/main/java/org/jabref/gui/sidepane/GroupsSidePaneComponent.java @@ -1,7 +1,5 @@ package org.jabref.gui.sidepane; -import java.util.EnumSet; - import javafx.collections.SetChangeListener; import javafx.scene.control.Button; import javafx.scene.control.ToggleButton; @@ -15,19 +13,23 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.logic.l10n.Localization; +import java.util.EnumSet; + public class GroupsSidePaneComponent extends SidePaneComponent { private final GroupsPreferences groupsPreferences; private final DialogService dialogService; - private final Button intersectionUnionToggle = IconTheme.JabRefIcons.GROUP_INTERSECTION.asButton(); + 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; @@ -36,11 +38,19 @@ public GroupsSidePaneComponent(SimpleCommand closeCommand, setupFilterToggle(); setupIntersectionUnionToggle(); - groupsPreferences.groupViewModeProperty().addListener((SetChangeListener) change -> { - GroupModeViewModel modeViewModel = new GroupModeViewModel(groupsPreferences.groupViewModeProperty()); - intersectionUnionToggle.setGraphic(modeViewModel.getUnionIntersectionGraphic()); - intersectionUnionToggle.setTooltip(modeViewModel.getUnionIntersectionTooltip()); - }); + groupsPreferences + .groupViewModeProperty() + .addListener( + (SetChangeListener) + change -> { + GroupModeViewModel modeViewModel = + new GroupModeViewModel( + groupsPreferences.groupViewModeProperty()); + intersectionUnionToggle.setGraphic( + modeViewModel.getUnionIntersectionGraphic()); + intersectionUnionToggle.setTooltip( + modeViewModel.getUnionIntersectionTooltip()); + }); } private void setupIntersectionUnionToggle() { @@ -51,15 +61,25 @@ private void setupIntersectionUnionToggle() { 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.setSelected( + groupsPreferences.groupViewModeProperty().contains(GroupViewMode.FILTER)); + 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.setSelected( + groupsPreferences.groupViewModeProperty().contains(GroupViewMode.INVERT)); + invertToggle + .selectedProperty() + .addListener( + (observable, oldValue, newValue) -> + groupsPreferences.setGroupViewMode(GroupViewMode.INVERT, newValue)); } private class ToggleUnionIntersectionAction extends SimpleCommand { diff --git a/src/main/java/org/jabref/gui/sidepane/SidePane.java b/src/main/java/org/jabref/gui/sidepane/SidePane.java index e4a969e663b8..0c27324dab27 100644 --- a/src/main/java/org/jabref/gui/sidepane/SidePane.java +++ b/src/main/java/org/jabref/gui/sidepane/SidePane.java @@ -1,10 +1,5 @@ package org.jabref.gui.sidepane; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.undo.UndoManager; - import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.collections.ListChangeListener; @@ -22,6 +17,11 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.undo.UndoManager; + public class SidePane extends VBox { private final SidePaneViewModel viewModel; private final GuiPreferences preferences; @@ -31,48 +31,53 @@ public class SidePane extends VBox { @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final Map visibleBindings = new HashMap<>(); - public SidePane(LibraryTabContainer tabContainer, - GuiPreferences preferences, - ChatHistoryService chatHistoryService, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor, - DialogService dialogService, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - UndoManager undoManager) { + public SidePane( + LibraryTabContainer tabContainer, + GuiPreferences preferences, + ChatHistoryService chatHistoryService, + JournalAbbreviationRepository abbreviationRepository, + TaskExecutor taskExecutor, + DialogService dialogService, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + ClipBoardManager clipBoardManager, + UndoManager undoManager) { this.stateManager = stateManager; this.preferences = preferences; - this.viewModel = new SidePaneViewModel( - tabContainer, - preferences, - chatHistoryService, - abbreviationRepository, - stateManager, - taskExecutor, - dialogService, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, - undoManager); + this.viewModel = + new SidePaneViewModel( + tabContainer, + preferences, + chatHistoryService, + abbreviationRepository, + stateManager, + taskExecutor, + dialogService, + fileUpdateMonitor, + entryTypesManager, + clipBoardManager, + undoManager); - stateManager.getVisibleSidePaneComponents().addListener((ListChangeListener) c -> updateView()); + 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( - () -> stateManager.getVisibleSidePaneComponents().contains(pane), - stateManager.getVisibleSidePaneComponents()); + BooleanBinding visibility = + Bindings.createBooleanBinding( + () -> stateManager.getVisibleSidePaneComponents().contains(pane), + stateManager.getVisibleSidePaneComponents()); visibleBindings.put(pane, visibility); return visibility; } diff --git a/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java b/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java index 6260a9081238..d392dfabcee3 100644 --- a/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java +++ b/src/main/java/org/jabref/gui/sidepane/SidePaneComponent.java @@ -23,11 +23,12 @@ public class SidePaneComponent extends BorderPane { 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; diff --git a/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java b/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java index 171fc8b87777..3bcc3081bdf1 100644 --- a/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java +++ b/src/main/java/org/jabref/gui/sidepane/SidePaneContentFactory.java @@ -1,7 +1,5 @@ package org.jabref.gui.sidepane; -import javax.swing.undo.UndoManager; - import javafx.scene.Node; import org.jabref.gui.ClipBoardManager; @@ -19,6 +17,8 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; +import javax.swing.undo.UndoManager; + public class SidePaneContentFactory { private final LibraryTabContainer tabContainer; private final GuiPreferences preferences; @@ -32,17 +32,18 @@ public class SidePaneContentFactory { private final ClipBoardManager clipBoardManager; private final UndoManager undoManager; - public SidePaneContentFactory(LibraryTabContainer tabContainer, - GuiPreferences preferences, - ChatHistoryService chatHistoryService, - JournalAbbreviationRepository abbreviationRepository, - TaskExecutor taskExecutor, - DialogService dialogService, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - UndoManager undoManager) { + public SidePaneContentFactory( + LibraryTabContainer tabContainer, + GuiPreferences preferences, + ChatHistoryService chatHistoryService, + JournalAbbreviationRepository abbreviationRepository, + TaskExecutor taskExecutor, + DialogService dialogService, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + ClipBoardManager clipBoardManager, + UndoManager undoManager) { this.tabContainer = tabContainer; this.preferences = preferences; this.chatHistoryService = chatHistoryService; @@ -58,31 +59,31 @@ public SidePaneContentFactory(LibraryTabContainer tabContainer, public Node create(SidePaneType sidePaneType) { return switch (sidePaneType) { - case GROUPS -> new GroupTreeView( - taskExecutor, - stateManager, - preferences, - dialogService, - chatHistoryService); - case OPEN_OFFICE -> new OpenOfficePanel( - tabContainer, - preferences, - preferences.getOpenOfficePreferences(), - preferences.getExternalApplicationsPreferences(), - preferences.getLayoutFormatterPreferences(), - preferences.getCitationKeyPatternPreferences(), - abbreviationRepository, - (UiTaskExecutor) taskExecutor, - dialogService, - stateManager, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, - undoManager).getContent(); - case WEB_SEARCH -> new WebSearchPaneView( - preferences, - dialogService, - stateManager); + case GROUPS -> + new GroupTreeView( + taskExecutor, + stateManager, + preferences, + dialogService, + chatHistoryService); + case OPEN_OFFICE -> + new OpenOfficePanel( + tabContainer, + preferences, + preferences.getOpenOfficePreferences(), + preferences.getExternalApplicationsPreferences(), + preferences.getLayoutFormatterPreferences(), + preferences.getCitationKeyPatternPreferences(), + abbreviationRepository, + (UiTaskExecutor) taskExecutor, + dialogService, + stateManager, + fileUpdateMonitor, + entryTypesManager, + clipBoardManager, + undoManager) + .getContent(); + case WEB_SEARCH -> new WebSearchPaneView(preferences, dialogService, stateManager); }; } } diff --git a/src/main/java/org/jabref/gui/sidepane/SidePaneType.java b/src/main/java/org/jabref/gui/sidepane/SidePaneType.java index ebfd8f0d8b7b..6c4b377c2864 100644 --- a/src/main/java/org/jabref/gui/sidepane/SidePaneType.java +++ b/src/main/java/org/jabref/gui/sidepane/SidePaneType.java @@ -10,9 +10,18 @@ * 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); + 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; diff --git a/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java b/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java index 9d13d9ef56a1..132526d29358 100644 --- a/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java +++ b/src/main/java/org/jabref/gui/sidepane/SidePaneViewModel.java @@ -1,15 +1,5 @@ package org.jabref.gui.sidepane; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.IntStream; - -import javax.swing.undo.UndoManager; - import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -26,10 +16,19 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +import javax.swing.undo.UndoManager; + public class SidePaneViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(SidePaneViewModel.class); @@ -40,62 +39,77 @@ public class SidePaneViewModel extends AbstractViewModel { private final SidePaneContentFactory sidePaneContentFactory; private final DialogService dialogService; - public SidePaneViewModel(LibraryTabContainer tabContainer, - GuiPreferences preferences, - ChatHistoryService chatHistoryService, - JournalAbbreviationRepository abbreviationRepository, - StateManager stateManager, - TaskExecutor taskExecutor, - DialogService dialogService, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - ClipBoardManager clipBoardManager, - UndoManager undoManager) { + public SidePaneViewModel( + LibraryTabContainer tabContainer, + GuiPreferences preferences, + ChatHistoryService chatHistoryService, + JournalAbbreviationRepository abbreviationRepository, + StateManager stateManager, + TaskExecutor taskExecutor, + DialogService dialogService, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + ClipBoardManager clipBoardManager, + UndoManager undoManager) { this.preferences = preferences; this.stateManager = stateManager; this.dialogService = dialogService; - this.sidePaneContentFactory = new SidePaneContentFactory( - tabContainer, - preferences, - chatHistoryService, - abbreviationRepository, - taskExecutor, - dialogService, - stateManager, - fileUpdateMonitor, - entryTypesManager, - clipBoardManager, - undoManager); + this.sidePaneContentFactory = + new SidePaneContentFactory( + tabContainer, + preferences, + chatHistoryService, + abbreviationRepository, + taskExecutor, + dialogService, + stateManager, + 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()) { - preferences.getSidePanePreferences().visiblePanes().remove(change.getRemoved().getFirst()); - } - } - }); + getPanes() + .addListener( + (ListChangeListener) + change -> { + while (change.next()) { + if (change.wasAdded()) { + preferences + .getSidePanePreferences() + .visiblePanes() + .add(change.getAddedSubList().getFirst()); + } else if (change.wasRemoved()) { + preferences + .getSidePanePreferences() + .visiblePanes() + .remove(change.getRemoved().getFirst()); + } + } + }); } 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(), - dialogService); - case WEB_SEARCH, OPEN_OFFICE -> new SidePaneComponent(pane, - new ClosePaneAction(pane), - new MoveUpAction(pane), - new MoveDownAction(pane), - sidePaneContentFactory); - }; + sidePaneComponent = + switch (pane) { + 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); + }; sidePaneComponentLookup.put(pane, sidePaneComponent); } return sidePaneComponent; @@ -106,9 +120,10 @@ protected SidePaneComponent getSidePaneComponent(SidePaneType pane) { * position next time. */ private void updatePreferredPositions() { - Map preferredPositions = new HashMap<>(preferences.getSidePanePreferences() - .getPreferredPositions()); - IntStream.range(0, getPanes().size()).forEach(i -> preferredPositions.put(getPanes().get(i), i)); + Map preferredPositions = + new HashMap<>(preferences.getSidePanePreferences().getPreferredPositions()); + IntStream.range(0, getPanes().size()) + .forEach(i -> preferredPositions.put(getPanes().get(i), i)); preferences.getSidePanePreferences().setPreferredPositions(preferredPositions); } diff --git a/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java b/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java index 864a3e70591e..121a8db8992a 100644 --- a/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java +++ b/src/main/java/org/jabref/gui/sidepane/TogglePaneAction.java @@ -9,7 +9,8 @@ public class TogglePaneAction extends SimpleCommand { private final SidePaneType pane; private final SidePanePreferences sidePanePreferences; - public TogglePaneAction(StateManager stateManager, SidePaneType pane, SidePanePreferences sidePanePreferences) { + public TogglePaneAction( + StateManager stateManager, SidePaneType pane, SidePanePreferences sidePanePreferences) { this.stateManager = stateManager; this.pane = pane; this.sidePanePreferences = sidePanePreferences; @@ -19,7 +20,9 @@ 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)); + stateManager + .getVisibleSidePaneComponents() + .sort(new SidePaneViewModel.PreferredIndexSort(sidePanePreferences)); } else { stateManager.getVisibleSidePaneComponents().remove(pane); } diff --git a/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java b/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java index f86eae44389d..c9eeac802528 100644 --- a/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java +++ b/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java @@ -1,8 +1,5 @@ package org.jabref.gui.slr; -import java.io.IOException; -import java.nio.file.Path; - import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; @@ -12,10 +9,12 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.study.Study; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; + public class EditExistingStudyAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(EditExistingStudyAction.class); @@ -32,7 +31,8 @@ public EditExistingStudyAction(DialogService dialogService, StateManager stateMa public void execute() { // The action works on the current library // This library has to be determined - if (stateManager.getActiveDatabase().isEmpty() || !stateManager.getActiveDatabase().get().isStudy()) { + if (stateManager.getActiveDatabase().isEmpty() + || !stateManager.getActiveDatabase().get().isStudy()) { return; } BibDatabaseContext bibDatabaseContext = stateManager.getActiveDatabase().get(); @@ -51,7 +51,11 @@ public void execute() { Study study; try { - study = new StudyYamlParser().parseStudyYamlFile(studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME)); + study = + new StudyYamlParser() + .parseStudyYamlFile( + studyDirectory.resolve( + StudyRepository.STUDY_DEFINITION_FILE_NAME)); } catch (IOException e) { dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), e); return; diff --git a/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java b/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java index 512bcfd075cd..db3d94005a82 100644 --- a/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java +++ b/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java @@ -1,9 +1,6 @@ package org.jabref.gui.slr; -import java.io.IOException; -import java.nio.file.Path; -import java.util.function.Supplier; - +import org.eclipse.jgit.api.errors.GitAPIException; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; @@ -20,11 +17,13 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; - -import org.eclipse.jgit.api.errors.GitAPIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Supplier; + public class ExistingStudySearchAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(ExistingStudySearchAction.class); @@ -51,7 +50,8 @@ public ExistingStudySearchAction( TaskExecutor taskExecutor, CliPreferences preferences, StateManager stateManager) { - this(tabContainer, + this( + tabContainer, openDatabaseActionSupplier, dialogService, fileUpdateMonitor, @@ -104,38 +104,53 @@ protected void crawl() { try { crawlPreparation(this.studyDirectory); } catch (IOException | GitAPIException e) { - dialogService.showErrorDialogAndWait(Localization.lang("Study repository could not be created"), 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); + crawler = + new Crawler( + this.studyDirectory, + new SlrGitHandler(this.studyDirectory), + preferences, + new BibEntryTypesManager(), + fileUpdateMonitor); } catch (IOException | ParseException 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); + 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); } /** diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java index 519b9899ed22..97ef9adaeb13 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java @@ -1,12 +1,8 @@ package org.jabref.gui.slr; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.StringJoiner; -import java.util.function.Consumer; -import java.util.stream.Stream; +import com.airhacks.afterburner.views.ViewLoader; + +import jakarta.inject.Inject; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleStringProperty; @@ -34,12 +30,17 @@ import org.jabref.gui.util.ViewModelTableRowFactory; import org.jabref.logic.l10n.Localization; import org.jabref.model.study.Study; - -import com.airhacks.afterburner.views.ViewLoader; -import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.function.Consumer; +import java.util.stream.Stream; + /** * This class controls the user interface of the study definition management dialog. The UI elements and their layout * are defined in the FXML file. @@ -99,9 +100,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); @@ -119,9 +118,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,47 +132,60 @@ 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())), + 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) { - viewModel.updateSelectedCatalogs(); - return viewModel.saveStudy(); - } - // Cancel button will return null - return null; - }); + viewModel.getDirectory().isEmpty()), + directoryWarning.visibleProperty())); + + setResultConverter( + button -> { + if (button == saveSurveyButtonType) { + viewModel.updateSelectedCatalogs(); + return viewModel.saveStudy(); + } + // Cancel button will return null + return null; + }); } @FXML private void initialize() { if (study.isEmpty()) { - viewModel = new ManageStudyDefinitionViewModel( - 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); + 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()); @@ -187,57 +197,76 @@ private void initialize() { private void updateDirectoryWarning(Path directory) { if (!Files.isDirectory(directory)) { - directoryWarning.setText(Localization.lang("Warning: The selected directory is not a valid directory.")); + directoryWarning.setText( + Localization.lang("Warning: The selected directory is not a valid directory.")); directoryWarning.setVisible(true); } else { try (Stream entries = Files.list(directory)) { if (entries.findAny().isPresent()) { - directoryWarning.setText(Localization.lang("Warning: The selected directory is not empty.")); + directoryWarning.setText( + Localization.lang("Warning: The selected directory is not empty.")); directoryWarning.setVisible(true); } else { directoryWarning.setVisible(false); } - } catch ( - IOException e) { - directoryWarning.setText(Localization.lang("Warning: Failed to check if the directory is empty.")); + } catch (IOException e) { + directoryWarning.setText( + Localization.lang("Warning: Failed to check if the directory is empty.")); directoryWarning.setVisible(true); } } } private void initAuthorTab() { - setupCommonPropertiesForTables(addAuthor, this::addAuthor, authorsColumn, authorsActionColumn); + setupCommonPropertiesForTables( + addAuthor, this::addAuthor, authorsColumn, authorsActionColumn); setupCellFactories(authorsColumn, authorsActionColumn, viewModel::deleteAuthor); authorTableView.setItems(viewModel.getAuthors()); } private void initQuestionsTab() { - setupCommonPropertiesForTables(addResearchQuestion, this::addResearchQuestion, questionsColumn, questionsActionColumn); + setupCommonPropertiesForTables( + addResearchQuestion, + this::addResearchQuestion, + questionsColumn, + questionsActionColumn); setupCellFactories(questionsColumn, questionsActionColumn, viewModel::deleteQuestion); questionTableView.setItems(viewModel.getResearchQuestions()); } private void initQueriesTab() { - setupCommonPropertiesForTables(addQuery, this::addQuery, queriesColumn, queriesActionColumn); + setupCommonPropertiesForTables( + addQuery, this::addQuery, queriesColumn, queriesActionColumn); setupCellFactories(queriesColumn, queriesActionColumn, viewModel::deleteQuery); queryTableView.setItems(viewModel.getQueries()); // 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())); + 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())); } private void initCatalogsTab() { new ViewModelTableRowFactory() - .withOnMouseClickedEvent((entry, event) -> { - if (event.getButton() == MouseButton.PRIMARY) { - entry.setEnabled(!entry.isEnabled()); - } - }) + .withOnMouseClickedEvent( + (entry, event) -> { + if (event.getButton() == MouseButton.PRIMARY) { + entry.setEnabled(!entry.isEnabled()); + } + }) .install(catalogTable); if (study.isEmpty()) { @@ -258,15 +287,17 @@ private void initCatalogsTab() { catalogTable.setItems(viewModel.getCatalogs()); } - private void setupCommonPropertiesForTables(Node addControl, - Runnable addAction, - TableColumn contentColumn, - TableColumn actionColumn) { - addControl.setOnKeyPressed(event -> { - if (event.getCode() == KeyCode.ENTER) { - addAction.run(); - } - }); + private void setupCommonPropertiesForTables( + Node addControl, + Runnable addAction, + TableColumn contentColumn, + TableColumn actionColumn) { + addControl.setOnKeyPressed( + event -> { + if (event.getCode() == KeyCode.ENTER) { + addAction.run(); + } + }); contentColumn.setReorderable(false); contentColumn.setCellFactory(TextFieldTableCell.forTableColumn()); @@ -274,16 +305,16 @@ 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)) + .withOnMouseClickedEvent(item -> evt -> removeAction.accept(item)) .install(actionColumn); } @@ -307,14 +338,17 @@ private void addQuery() { @FXML public void selectStudyDirectory() { - DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(pathToStudyDataDirectory) - .build(); - - Optional selectedDirectoryOptional = dialogService.showDirectorySelectionDialog(directoryDialogConfiguration); - selectedDirectoryOptional.ifPresent(selectedDirectory -> { - viewModel.setStudyDirectory(Optional.of(selectedDirectory)); - updateDirectoryWarning(selectedDirectory); - }); + DirectoryDialogConfiguration directoryDialogConfiguration = + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(pathToStudyDataDirectory) + .build(); + + Optional selectedDirectoryOptional = + dialogService.showDirectorySelectionDialog(directoryDialogConfiguration); + selectedDirectoryOptional.ifPresent( + selectedDirectory -> { + viewModel.setStudyDirectory(Optional.of(selectedDirectory)); + updateDirectoryWarning(selectedDirectory); + }); } } diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java index 8080c64990ad..c71fe6c3b710 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java @@ -1,14 +1,5 @@ package org.jabref.gui.slr; -import java.io.IOException; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - import javafx.beans.property.Property; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -33,22 +24,32 @@ import org.jabref.model.study.Study; import org.jabref.model.study.StudyDatabase; import org.jabref.model.study.StudyQuery; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + /** * 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 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(); @@ -66,21 +67,25 @@ public class ManageStudyDefinitionViewModel { /** * Constructor for a new study */ - public ManageStudyDefinitionViewModel(ImportFormatPreferences importFormatPreferences, - 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 -> !name.equals(CompositeSearchBasedFetcher.FETCHER_NAME)) - .map(name -> { - boolean enabled = DEFAULT_SELECTION.contains(name); - return new StudyCatalogItem(name, enabled); - }) - .toList()); + public ManageStudyDefinitionViewModel( + ImportFormatPreferences importFormatPreferences, + 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 -> !name.equals(CompositeSearchBasedFetcher.FETCHER_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); } @@ -91,29 +96,34 @@ public ManageStudyDefinitionViewModel(ImportFormatPreferences importFormatPrefer * @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()); researchQuestions.addAll(study.getResearchQuestions()); 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 -> !name.equals(CompositeSearchBasedFetcher.FETCHER_NAME)) - .map(name -> { - boolean enabled = studyDatabases.contains(new StudyDatabase(name, true)); - return new StudyCatalogItem(name, enabled); - }) - .toList()); + 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 -> !name.equals(CompositeSearchBasedFetcher.FETCHER_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); @@ -166,39 +176,53 @@ public void addQuery(String query) { } public SlrStudyAndDirectory saveStudy() { - 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())); + 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())); Path studyDirectory; final String studyDirectoryAsString = directory.getValueSafe(); try { studyDirectory = Path.of(studyDirectoryAsString); } catch (InvalidPathException e) { LOGGER.error("Invalid path was provided: {}", studyDirectoryAsString); - dialogService.notify(Localization.lang("Unable to write to %0.", 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. return null; } - Path studyDefinitionFile = studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME); + Path studyDefinitionFile = + studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME); try { new StudyYamlParser().writeStudyYamlFile(study, studyDefinitionFile); } 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); + new GitHandler(studyDirectory) + .createCommitOnCurrentBranch("Update study definition", false); } catch (Exception e) { - LOGGER.error("Could not commit study definition file in directory {}", studyDirectory, e); - dialogService.notify(Localization.lang("Please enter a valid file path.") + - ": " + studyDirectory); + LOGGER.error( + "Could not commit study definition file in directory {}", studyDirectory, e); + dialogService.notify( + Localization.lang("Please enter a valid file path.") + ": " + studyDirectory); // We continue nevertheless as the directory itself could be valid } @@ -210,7 +234,11 @@ public Property titleProperty() { } public void setStudyDirectory(Optional studyRepositoryRoot) { - getDirectory().setValue(studyRepositoryRoot.map(Path::toString).orElseGet(() -> getDirectory().getValueSafe())); + getDirectory() + .setValue( + studyRepositoryRoot + .map(Path::toString) + .orElseGet(() -> getDirectory().getValueSafe())); } public void deleteAuthor(String item) { @@ -233,10 +261,11 @@ public void initializeSelectedCatalogs() { } public void updateSelectedCatalogs() { - List selectedCatalogsList = databases.stream() - .filter(StudyCatalogItem::isEnabled) - .map(StudyCatalogItem::getName) - .collect(Collectors.toList()); + List selectedCatalogsList = + databases.stream() + .filter(StudyCatalogItem::isEnabled) + .map(StudyCatalogItem::getName) + .collect(Collectors.toList()); workspacePreferences.setSelectedSlrCatalogs(selectedCatalogsList); } diff --git a/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java b/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java index 223d99d52244..3460510b6073 100644 --- a/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java +++ b/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java @@ -1,10 +1,10 @@ package org.jabref.gui.slr; +import org.jabref.model.study.Study; + import java.nio.file.Path; import java.util.Objects; -import org.jabref.model.study.Study; - public class SlrStudyAndDirectory { private final Study study; private final Path studyDirectory; diff --git a/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java b/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java index 3b2b34f105b8..ccf985997d7f 100644 --- a/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java +++ b/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java @@ -1,10 +1,6 @@ package org.jabref.gui.slr; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.function.Supplier; - +import org.eclipse.jgit.api.errors.GitAPIException; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; @@ -17,11 +13,14 @@ import org.jabref.logic.util.TaskExecutor; import org.jabref.model.study.Study; import org.jabref.model.util.FileUpdateMonitor; - -import org.eclipse.jgit.api.errors.GitAPIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Supplier; + /** * Used to start a new study: *
      @@ -38,14 +37,16 @@ public class StartNewStudyAction extends ExistingStudySearchAction { Study newStudy; - public StartNewStudyAction(LibraryTabContainer tabContainer, - Supplier openDatabaseActionSupplier, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - CliPreferences preferences, - StateManager stateManager, - DialogService dialogService) { - super(tabContainer, + public StartNewStudyAction( + LibraryTabContainer tabContainer, + Supplier openDatabaseActionSupplier, + FileUpdateMonitor fileUpdateMonitor, + TaskExecutor taskExecutor, + CliPreferences preferences, + StateManager stateManager, + DialogService dialogService) { + super( + tabContainer, openDatabaseActionSupplier, dialogService, fileUpdateMonitor, @@ -58,10 +59,12 @@ public StartNewStudyAction(LibraryTabContainer tabContainer, @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); @@ -69,8 +72,10 @@ protected void crawlPreparation(Path studyRepositoryRoot) throws IOException, Gi @Override public void execute() { - Optional studyAndDirectory = dialogService.showCustomDialogAndWait( - new ManageStudyDefinitionView(preferences.getFilePreferences().getWorkingDirectory())); + Optional studyAndDirectory = + dialogService.showCustomDialogAndWait( + new ManageStudyDefinitionView( + preferences.getFilePreferences().getWorkingDirectory())); if (studyAndDirectory.isEmpty()) { return; } diff --git a/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java b/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java index b379d373bf95..7eeb3d2e442d 100644 --- a/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java +++ b/src/main/java/org/jabref/gui/slr/StudyCatalogItem.java @@ -1,7 +1,5 @@ package org.jabref.gui.slr; -import java.util.Objects; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -9,6 +7,8 @@ import org.jabref.model.study.StudyDatabase; +import java.util.Objects; + /** * View representation of {@link StudyDatabase} */ @@ -47,10 +47,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 @@ -62,7 +59,8 @@ public boolean equals(Object o) { return false; } StudyCatalogItem that = (StudyCatalogItem) o; - return Objects.equals(getName(), that.getName()) && Objects.equals(isEnabled(), that.isEnabled()); + return Objects.equals(getName(), that.getName()) + && Objects.equals(isEnabled(), that.isEnabled()); } @Override diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java index 990c81cd3d35..4861c77bfddc 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldAction.java @@ -1,13 +1,5 @@ package org.jabref.gui.specialfields; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Supplier; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -21,10 +13,17 @@ import org.jabref.model.FieldChange; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.SpecialField; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; + +import javax.swing.undo.UndoManager; + public class SpecialFieldAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(SpecialFieldAction.class); @@ -41,15 +40,16 @@ public class SpecialFieldAction extends SimpleCommand { /** * @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 +73,11 @@ 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))); } @@ -92,7 +95,8 @@ public void execute() { dialogService.notify(outText); } - // if user does not change anything with his action, we do not do anything either, even no output message + // 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); } @@ -101,21 +105,28 @@ public void execute() { private String getTextDone(SpecialField field, String... params) { Objects.requireNonNull(params); - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, preferences, undoManager); + SpecialFieldViewModel viewModel = + new SpecialFieldViewModel(field, preferences, undoManager); 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)) { + 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)) { // setting a multi value special field - the setted value is displayed, too String[] allParams = {viewModel.getLocalization(), params[0], params[1]}; return Localization.lang("Set '%0' to '%1' for %2 entries", allParams); } 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]); + return Localization.lang( + "Cleared '%0' for %1 entries", viewModel.getLocalization(), params[0]); } else { // invalid usage - LOGGER.info("Creation of special field status change message failed: illegal argument combination."); + LOGGER.info( + "Creation of special field status change message failed: illegal argument combination."); return ""; } } diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java index 63ac0bbf84f5..9841df1b5aae 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldMenuItemFactory.java @@ -1,9 +1,6 @@ package org.jabref.gui.specialfields; -import java.util.function.Function; -import java.util.function.Supplier; - -import javax.swing.undo.UndoManager; +import de.saxsys.mvvmfx.utils.commands.Command; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; @@ -16,51 +13,80 @@ import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.SpecialFieldValue; -import de.saxsys.mvvmfx.utils.commands.Command; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.swing.undo.UndoManager; public class SpecialFieldMenuItemFactory { - 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)); - menuItem.visibleProperty().bind(preferences.getSpecialFieldsPreferences().specialFieldsEnabledProperty()); + 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)); + 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) { - SpecialFieldViewModel viewModel = new SpecialFieldViewModel(field, preferences, undoManager); + 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()); + menu.visibleProperty() + .bind(preferences.getSpecialFieldsPreferences().specialFieldsEnabledProperty()); return menu; } } diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java index e2d3ffda9a69..c7e0ee29c83f 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldValueViewModel.java @@ -1,14 +1,14 @@ package org.jabref.gui.specialfields; -import java.util.Objects; -import java.util.Optional; - import org.jabref.gui.actions.Action; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.icon.JabRefIcon; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.SpecialFieldValue; +import java.util.Objects; +import java.util.Optional; + public class SpecialFieldValueViewModel { private final SpecialFieldValue value; diff --git a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java index e2f4387bb39e..5c725cd3f60f 100644 --- a/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java +++ b/src/main/java/org/jabref/gui/specialfields/SpecialFieldViewModel.java @@ -1,13 +1,5 @@ package org.jabref.gui.specialfields; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import javax.swing.undo.UndoManager; - import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; @@ -22,13 +14,22 @@ import org.jabref.model.entry.field.SpecialField; import org.jabref.model.entry.field.SpecialFieldValue; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + public class SpecialFieldViewModel { private final SpecialField field; private final CliPreferences preferences; private final UndoManager undoManager; - public SpecialFieldViewModel(SpecialField field, CliPreferences preferences, UndoManager undoManager) { + public SpecialFieldViewModel( + SpecialField field, CliPreferences preferences, UndoManager undoManager) { this.field = Objects.requireNonNull(field); this.preferences = Objects.requireNonNull(preferences); this.undoManager = Objects.requireNonNull(undoManager); @@ -38,15 +39,17 @@ public SpecialField getField() { return field; } - public SpecialFieldAction getSpecialFieldAction(SpecialFieldValue value, - Supplier tabSupplier, - DialogService dialogService, - StateManager stateManager) { + 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 + // 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, @@ -80,12 +83,17 @@ public JabRefIcon getEmptyIcon() { public List getValues() { return field.getValues().stream() - .map(SpecialFieldValueViewModel::new) - .collect(Collectors.toList()); + .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))); } diff --git a/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java b/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java index 8c312f5b042c..d1517b632644 100644 --- a/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java +++ b/src/main/java/org/jabref/gui/texparser/CitationsDisplay.java @@ -1,9 +1,5 @@ package org.jabref.gui.texparser; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Node; @@ -21,15 +17,20 @@ import org.jabref.model.strings.LatexToUnicodeAdapter; import org.jabref.model.texparser.Citation; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + public class CitationsDisplay extends ListView { private final ObjectProperty basePath; public CitationsDisplay() { this.basePath = new SimpleObjectProperty<>(null); - new ViewModelListCellFactory().withGraphic(this::getDisplayGraphic) - .withTooltip(this::getDisplayTooltip) - .install(this); + new ViewModelListCellFactory() + .withGraphic(this::getDisplayGraphic) + .withTooltip(this::getDisplayTooltip) + .install(this); this.getStyleClass().add("citationsList"); } @@ -51,7 +52,8 @@ private Node getDisplayGraphic(Citation item) { Label fileNameLabel = new Label("%s".formatted(basePath.get().relativize(item.path()))); fileNameLabel.setGraphic(IconTheme.JabRefIcons.LATEX_FILE.getGraphicNode()); - Label positionLabel = new Label("(%s:%s-%s)".formatted(item.line(), item.colStart(), item.colEnd())); + Label positionLabel = + new Label("(%s:%s-%s)".formatted(item.line(), item.colStart(), item.colEnd())); positionLabel.setGraphic(IconTheme.JabRefIcons.LATEX_LINE.getGraphicNode()); HBox dataBox = new HBox(5, fileNameLabel, positionLabel); diff --git a/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java b/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java index 637feed20fb1..94c65e178e3f 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java +++ b/src/main/java/org/jabref/gui/texparser/ParseLatexAction.java @@ -1,13 +1,13 @@ package org.jabref.gui.texparser; +import com.airhacks.afterburner.injection.Injector; + import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.model.database.BibDatabaseContext; -import com.airhacks.afterburner.injection.Injector; - public class ParseLatexAction extends SimpleCommand { private final StateManager stateManager; @@ -20,7 +20,8 @@ public ParseLatexAction(StateManager stateManager) { @Override public void execute() { DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); - BibDatabaseContext database = stateManager.getActiveDatabase().orElseThrow(NullPointerException::new); + BibDatabaseContext database = + stateManager.getActiveDatabase().orElseThrow(NullPointerException::new); dialogService.showCustomDialogAndWait(new ParseLatexDialogView(database)); } } diff --git a/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java b/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java index bf1a88fe7851..959eb90c3e99 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java +++ b/src/main/java/org/jabref/gui/texparser/ParseLatexDialogView.java @@ -1,5 +1,12 @@ package org.jabref.gui.texparser; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; + +import jakarta.inject.Inject; + import javafx.beans.binding.Bindings; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -9,6 +16,7 @@ import javafx.scene.control.SelectionMode; import javafx.scene.control.TextField; +import org.controlsfx.control.CheckTreeView; import org.jabref.gui.DialogService; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BaseDialog; @@ -23,12 +31,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import de.saxsys.mvvmfx.utils.validation.visualization.ControlsFxVisualizer; -import jakarta.inject.Inject; -import org.controlsfx.control.CheckTreeView; - public class ParseLatexDialogView extends BaseDialog { private final BibDatabaseContext databaseContext; @@ -56,38 +58,60 @@ public ParseLatexDialogView(BibDatabaseContext databaseContext) { ViewLoader.view(this).load().setAsDialogPane(this); - ControlHelper.setAction(parseButtonType, getDialogPane(), event -> viewModel.parseButtonClicked()); + 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); - EasyBind.subscribe(fileTreeView.rootProperty(), root -> { - ((CheckBoxTreeItem) root).setSelected(true); - root.setExpanded(true); - EasyBind.bindContent(viewModel.getCheckedFileList(), fileTreeView.getCheckModel().getCheckedItems()); - }); - - latexDirectoryField.textProperty().bindBidirectional(viewModel.latexFileDirectoryProperty()); + EasyBind.subscribe( + fileTreeView.rootProperty(), + root -> { + ((CheckBoxTreeItem) root).setSelected(true); + root.setExpanded(true); + EasyBind.bindContent( + viewModel.getCheckedFileList(), + fileTreeView.getCheckModel().getCheckedItems()); + }); + + latexDirectoryField + .textProperty() + .bindBidirectional(viewModel.latexFileDirectoryProperty()); validationVisualizer.setDecoration(new IconValidationDecorator()); - validationVisualizer.initVisualization(viewModel.latexDirectoryValidation(), latexDirectoryField); + validationVisualizer.initVisualization( + viewModel.latexDirectoryValidation(), latexDirectoryField); browseButton.disableProperty().bindBidirectional(viewModel.searchInProgressProperty()); - searchButton.disableProperty().bind(viewModel.latexDirectoryValidation().validProperty().not()); + searchButton + .disableProperty() + .bind(viewModel.latexDirectoryValidation().validProperty().not()); selectAllButton.disableProperty().bindBidirectional(viewModel.noFilesFoundProperty()); unselectAllButton.disableProperty().bindBidirectional(viewModel.noFilesFoundProperty()); progressIndicator.visibleProperty().bindBidirectional(viewModel.searchInProgressProperty()); diff --git a/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java b/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java index f0194c0a5b94..3379633da299 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/ParseLatexDialogViewModel.java @@ -1,14 +1,9 @@ package org.jabref.gui.texparser; -import java.io.IOException; -import java.nio.file.FileSystemException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; @@ -34,14 +29,19 @@ import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.FileUpdateMonitor; - -import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; -import de.saxsys.mvvmfx.utils.validation.ValidationMessage; -import de.saxsys.mvvmfx.utils.validation.ValidationStatus; -import de.saxsys.mvvmfx.utils.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.FileSystemException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + public class ParseLatexDialogViewModel extends AbstractViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(ParseLatexDialogViewModel.class); @@ -59,19 +59,31 @@ public class ParseLatexDialogViewModel extends AbstractViewModel { 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); @@ -79,8 +91,12 @@ public ParseLatexDialogViewModel(BibDatabaseContext databaseContext, this.successfulSearch = new SimpleBooleanProperty(false); Predicate isDirectory = path -> Path.of(path).toFile().isDirectory(); - latexDirectoryValidator = new FunctionBasedValidator<>(latexFileDirectory, isDirectory, - ValidationMessage.error(Localization.lang("Please enter a valid file path."))); + latexDirectoryValidator = + new FunctionBasedValidator<>( + latexFileDirectory, + isDirectory, + ValidationMessage.error( + Localization.lang("Please enter a valid file path."))); } public StringProperty latexFileDirectoryProperty() { @@ -112,13 +128,20 @@ public BooleanProperty successfulSearchProperty() { } public void browseButtonClicked() { - DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(Path.of(latexFileDirectory.get())).build(); + DirectoryDialogConfiguration directoryDialogConfiguration = + new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(Path.of(latexFileDirectory.get())) + .build(); - dialogService.showDirectorySelectionDialog(directoryDialogConfiguration).ifPresent(selectedDirectory -> { - latexFileDirectory.set(selectedDirectory.toAbsolutePath().toString()); - preferences.getFilePreferences().setWorkingDirectory(selectedDirectory.toAbsolutePath()); - }); + dialogService + .showDirectorySelectionDialog(directoryDialogConfiguration) + .ifPresent( + selectedDirectory -> { + latexFileDirectory.set(selectedDirectory.toAbsolutePath().toString()); + preferences + .getFilePreferences() + .setWorkingDirectory(selectedDirectory.toAbsolutePath()); + }); } /** @@ -126,26 +149,33 @@ public void browseButtonClicked() { */ 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); + .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())); + dialogService.showErrorDialogAndWait( + Localization.lang("JabRef does not have permission to access %s") + .formatted(exception.getCause().getMessage())); } else { dialogService.showErrorDialogAndWait(exception); } @@ -160,17 +190,22 @@ private FileNodeViewModel searchDirectory(Path directory) throws IOException { Map> fileListPartition; try (Stream filesStream = Files.list(directory)) { - fileListPartition = filesStream.collect(Collectors.partitioningBy(path -> path.toFile().isDirectory())); + fileListPartition = + filesStream.collect( + Collectors.partitioningBy(path -> path.toFile().isDirectory())); } catch (IOException e) { - LOGGER.error("%s while searching files: %s".formatted(e.getClass().getName(), e.getMessage()), e); + LOGGER.error( + "%s while searching files: %s" + .formatted(e.getClass().getName(), e.getMessage()), + e); return parent; } List subDirectories = fileListPartition.get(true); - List files = fileListPartition.get(false) - .stream() - .filter(path -> path.toString().endsWith(TEX_EXT)) - .toList(); + List files = + fileListPartition.get(false).stream() + .filter(path -> path.toString().endsWith(TEX_EXT)) + .toList(); int fileCount = 0; for (Path subDirectory : subDirectories) { @@ -183,9 +218,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; } @@ -193,26 +226,33 @@ private FileNodeViewModel searchDirectory(Path directory) throws IOException { * Parse all checked files in a background task. */ public void parseButtonClicked() { - List fileList = checkedFileList.stream() - .map(item -> item.getValue().getPath()) - .filter(path -> path.toFile().isFile()) - .toList(); + List fileList = + checkedFileList.stream() + .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/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java b/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java index 7fc00e8b7eb5..64e5f0a652a6 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java +++ b/src/main/java/org/jabref/gui/texparser/ParseLatexResultView.java @@ -1,6 +1,9 @@ package org.jabref.gui.texparser; -import java.nio.file.Path; +import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; + +import jakarta.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.Button; @@ -16,9 +19,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.texparser.LatexBibEntriesResolverResult; -import com.airhacks.afterburner.views.ViewLoader; -import com.tobiasdiez.easybind.EasyBind; -import jakarta.inject.Inject; +import java.nio.file.Path; public class ParseLatexResultView extends BaseDialog { @@ -31,7 +32,10 @@ public class ParseLatexResultView extends BaseDialog { @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; @@ -40,10 +44,13 @@ public ParseLatexResultView(LatexBibEntriesResolverResult resolverResult, BibDat ViewLoader.view(this).load().setAsDialogPane(this); - ControlHelper.setAction(importButtonType, getDialogPane(), event -> { - viewModel.importButtonClicked(); - close(); - }); + ControlHelper.setAction( + importButtonType, + getDialogPane(), + event -> { + viewModel.importButtonClicked(); + close(); + }); Button importButton = (Button) getDialogPane().lookupButton(importButtonType); importButton.disableProperty().bind(viewModel.importButtonDisabledProperty()); @@ -57,16 +64,18 @@ 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; - }) + .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(), + EasyBind.subscribe( + referenceListView.getSelectionModel().selectedItemProperty(), viewModel::activeReferenceChanged); citationsDisplay.basePathProperty().set(basePath); diff --git a/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java b/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java index ba72bc933b35..4ec5ac333683 100644 --- a/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/ParseLatexResultViewModel.java @@ -1,9 +1,6 @@ package org.jabref.gui.texparser; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; +import com.airhacks.afterburner.injection.Injector; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyListWrapper; @@ -21,7 +18,10 @@ import org.jabref.model.texparser.Citation; import org.jabref.model.texparser.LatexBibEntriesResolverResult; -import com.airhacks.afterburner.injection.Injector; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; public class ParseLatexResultViewModel extends AbstractViewModel { @@ -31,19 +31,27 @@ public class ParseLatexResultViewModel extends AbstractViewModel { private final ObservableList citationList; private final BooleanProperty importButtonDisabled; - public ParseLatexResultViewModel(LatexBibEntriesResolverResult resolverResult, BibDatabaseContext databaseContext) { + public ParseLatexResultViewModel( + LatexBibEntriesResolverResult resolverResult, BibDatabaseContext databaseContext) { this.resolverResult = resolverResult; this.databaseContext = databaseContext; this.referenceList = FXCollections.observableArrayList(); this.citationList = FXCollections.observableArrayList(); - Set newEntryKeys = resolverResult.getNewEntries().stream().map(entry -> entry.getCitationKey().orElse("")).collect(Collectors.toSet()); - for (Map.Entry> entry : resolverResult.getCitations().asMap().entrySet()) { + 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())); + 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() { @@ -74,7 +82,11 @@ 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/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java b/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java index 665f13dd55e5..470f54b6b0d0 100644 --- a/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java +++ b/src/main/java/org/jabref/gui/texparser/ReferenceViewModel.java @@ -1,20 +1,21 @@ package org.jabref.gui.texparser; -import java.util.Collection; - import javafx.beans.property.ReadOnlyListWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jabref.model.texparser.Citation; +import java.util.Collection; + public class ReferenceViewModel { private final String entry; private final boolean highlighted; private final ObservableList citationList; - public ReferenceViewModel(String entry, boolean highlighted, Collection citationColl) { + public ReferenceViewModel( + String entry, boolean highlighted, Collection citationColl) { this.entry = entry; this.highlighted = highlighted; this.citationList = FXCollections.observableArrayList(); @@ -39,9 +40,7 @@ public String getDisplayText() { @Override public String toString() { - return "ReferenceViewModel{entry='%s', highlighted=%s, citationList=%s}".formatted( - this.entry, - this.highlighted, - this.citationList); + return "ReferenceViewModel{entry='%s', highlighted=%s, citationList=%s}" + .formatted(this.entry, this.highlighted, this.citationList); } } diff --git a/src/main/java/org/jabref/gui/theme/StyleSheet.java b/src/main/java/org/jabref/gui/theme/StyleSheet.java index d922ce43a445..9a1ef6cfcf81 100644 --- a/src/main/java/org/jabref/gui/theme/StyleSheet.java +++ b/src/main/java/org/jabref/gui/theme/StyleSheet.java @@ -1,5 +1,10 @@ package org.jabref.gui.theme; +import org.jabref.architecture.AllowedToUseClassGetResource; +import org.jabref.gui.JabRefGUI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -9,12 +14,6 @@ import java.nio.file.Path; import java.util.Optional; -import org.jabref.architecture.AllowedToUseClassGetResource; -import org.jabref.gui.JabRefGUI; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - @AllowedToUseClassGetResource("JavaFX internally handles the passed URLs properly.") abstract class StyleSheet { @@ -41,9 +40,17 @@ static Optional create(String name) { 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); + 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); + LOGGER.warn( + "Cannot load additional css url {} because it is a malformed url: {}", + name, + e.getLocalizedMessage(), + e); } } @@ -57,12 +64,16 @@ static Optional create(String name) { 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. } diff --git a/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java b/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java index 04045faa41b5..87a323f83ccb 100644 --- a/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java +++ b/src/main/java/org/jabref/gui/theme/StyleSheetDataUrl.java @@ -25,7 +25,9 @@ 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 diff --git a/src/main/java/org/jabref/gui/theme/StyleSheetFile.java b/src/main/java/org/jabref/gui/theme/StyleSheetFile.java index 9401e51b18bb..c21d53a97cbd 100644 --- a/src/main/java/org/jabref/gui/theme/StyleSheetFile.java +++ b/src/main/java/org/jabref/gui/theme/StyleSheetFile.java @@ -1,5 +1,10 @@ package org.jabref.gui.theme; +import com.google.common.base.Strings; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -11,10 +16,6 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import com.google.common.base.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - final class StyleSheetFile extends StyleSheet { /** @@ -76,7 +77,8 @@ public URL getSceneStylesheet() { } if (Files.isDirectory(path)) { - LOGGER.warn("Failed to loadCannot load additional css {} because it is a directory.", path); + LOGGER.warn( + "Failed to loadCannot load additional css {} because it is a directory.", path); return null; } @@ -112,11 +114,14 @@ static Optional getDataUrl(URL url) { try (InputStream inputStream = conn.getInputStream()) { byte[] data = inputStream.readNBytes(MAX_IN_MEMORY_CSS_LENGTH); if (data.length < MAX_IN_MEMORY_CSS_LENGTH) { - String embeddedDataUrl = DATA_URL_PREFIX + Base64.getEncoder().encodeToString(data); + 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 { - LOGGER.trace("Not embedding css in data URL as the length is >= {}", MAX_IN_MEMORY_CSS_LENGTH); + LOGGER.trace( + "Not embedding css in data URL as the length is >= {}", + MAX_IN_MEMORY_CSS_LENGTH); } } } catch (IOException e) { diff --git a/src/main/java/org/jabref/gui/theme/Theme.java b/src/main/java/org/jabref/gui/theme/Theme.java index 03e6e414aed9..842f008a06e1 100644 --- a/src/main/java/org/jabref/gui/theme/Theme.java +++ b/src/main/java/org/jabref/gui/theme/Theme.java @@ -12,7 +12,9 @@ public class Theme { public enum Type { - DEFAULT, EMBEDDED, CUSTOM + DEFAULT, + EMBEDDED, + CUSTOM } public static final String BASE_CSS = "Base.css"; @@ -40,7 +42,7 @@ public Theme(String name) { } } else { this.additionalStylesheet = StyleSheet.create(name); - if (this.additionalStylesheet.isPresent()) { + if (this.additionalStylesheet.isPresent()) { this.type = Type.CUSTOM; this.name = name; } else { @@ -111,9 +113,6 @@ public int hashCode() { @Override public String toString() { - return "Theme{" + - "type=" + type + - ", name='" + name + '\'' + - '}'; + return "Theme{" + "type=" + type + ", name='" + name + '\'' + '}'; } } diff --git a/src/main/java/org/jabref/gui/theme/ThemeManager.java b/src/main/java/org/jabref/gui/theme/ThemeManager.java index eda4b251f902..a7d40a886ad5 100644 --- a/src/main/java/org/jabref/gui/theme/ThemeManager.java +++ b/src/main/java/org/jabref/gui/theme/ThemeManager.java @@ -1,15 +1,6 @@ package org.jabref.gui.theme; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.function.Consumer; +import com.google.common.annotations.VisibleForTesting; import javafx.application.ColorScheme; import javafx.application.Platform; @@ -24,11 +15,20 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.util.FileUpdateListener; import org.jabref.model.util.FileUpdateMonitor; - -import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.Consumer; + /** * 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 @@ -44,9 +44,10 @@ */ 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); @@ -60,9 +61,10 @@ public class ThemeManager { 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); @@ -70,16 +72,25 @@ public ThemeManager(WorkspacePreferences workspacePreferences, this.baseStyleSheet = StyleSheet.create(Theme.BASE_CSS).get(); this.theme = workspacePreferences.getTheme(); - // 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.mainFontSizeProperty(), size -> updateFontSettings()); - BindingsHelper.subscribeFuture(Platform.getPreferences().colorSchemeProperty(), colorScheme -> updateThemeSettings()); + BindingsHelper.subscribeFuture( + workspacePreferences.themeProperty(), theme -> updateThemeSettings()); + BindingsHelper.subscribeFuture( + workspacePreferences.themeSyncOsProperty(), theme -> updateThemeSettings()); + BindingsHelper.subscribeFuture( + workspacePreferences.shouldOverrideDefaultFontSizeProperty(), + should -> updateFontSettings()); + BindingsHelper.subscribeFuture( + workspacePreferences.mainFontSizeProperty(), size -> updateFontSettings()); + BindingsHelper.subscribeFuture( + Platform.getPreferences().colorSchemeProperty(), + colorScheme -> updateThemeSettings()); updateThemeSettings(); } @@ -103,15 +114,20 @@ private void updateThemeSettings() { this.theme = newTheme; LOGGER.info("Theme set to {} with base css {}", newTheme, baseStyleSheet); - this.theme.getAdditionalStylesheet().ifPresent( - styleSheet -> addStylesheetToWatchlist(styleSheet, this::additionalCssLiveUpdate)); + this.theme + .getAdditionalStylesheet() + .ifPresent( + styleSheet -> + addStylesheetToWatchlist( + styleSheet, this::additionalCssLiveUpdate)); additionalCssLiveUpdate(); updateFontSettings(); } private void updateFontSettings() { - UiTaskExecutor.runInJavaFXThread(() -> updateRunner.accept(() -> updateFontStyle(mainWindowScene))); + UiTaskExecutor.runInJavaFXThread( + () -> updateRunner.accept(() -> updateFontStyle(mainWindowScene))); } private void removeStylesheetFromWatchList(StyleSheet styleSheet) { @@ -146,26 +162,38 @@ private void baseCssLiveUpdate() { } private void additionalCssLiveUpdate() { - final String newStyleSheetLocation = this.theme.getAdditionalStylesheet().map(styleSheet -> { - styleSheet.reload(); - return styleSheet.getWebEngineStylesheet(); - }).orElse(""); - - LOGGER.debug("Updating additional CSS for main window scene and {} web engines", webEngines.size()); - - 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); - }); - }) - ); + final String newStyleSheetLocation = + this.theme + .getAdditionalStylesheet() + .map( + styleSheet -> { + styleSheet.reload(); + return styleSheet.getWebEngineStylesheet(); + }) + .orElse(""); + + LOGGER.debug( + "Updating additional CSS for main window scene and {} web engines", + webEngines.size()); + + 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); + }); + })); } private void updateBaseCss() { @@ -186,18 +214,23 @@ 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(""))); } /** @@ -208,11 +241,12 @@ private void updateAdditionalCss() { */ public void installCss(Scene mainWindowScene) { Objects.requireNonNull(mainWindowScene, "scene is required"); - updateRunner.accept(() -> { - this.mainWindowScene = mainWindowScene; - updateBaseCss(); - updateAdditionalCss(); - }); + updateRunner.accept( + () -> { + this.mainWindowScene = mainWindowScene; + updateBaseCss(); + updateAdditionalCss(); + }); } /** @@ -222,12 +256,18 @@ public void installCss(Scene mainWindowScene) { * @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() : ""); - } - }); + updateRunner.accept( + () -> { + if (this.webEngines.add(webEngine)) { + webEngine.setUserStyleSheetLocation( + this.theme.getAdditionalStylesheet().isPresent() + ? this.theme + .getAdditionalStylesheet() + .get() + .getWebEngineStylesheet() + : ""); + } + }); } /** @@ -242,9 +282,12 @@ public void updateFontStyle(Scene scene) { } if (workspacePreferences.shouldOverrideDefaultFontSize()) { - scene.getRoot().setStyle("-fx-font-size: " + workspacePreferences.getMainFontSize() + "pt;"); + scene.getRoot() + .setStyle("-fx-font-size: " + workspacePreferences.getMainFontSize() + "pt;"); } else { - scene.getRoot().setStyle("-fx-font-size: " + workspacePreferences.getDefaultFontSize() + "pt;"); + scene.getRoot() + .setStyle( + "-fx-font-size: " + workspacePreferences.getDefaultFontSize() + "pt;"); } } diff --git a/src/main/java/org/jabref/gui/theme/ThemeTypes.java b/src/main/java/org/jabref/gui/theme/ThemeTypes.java index 73523ae9c475..6032990ad150 100644 --- a/src/main/java/org/jabref/gui/theme/ThemeTypes.java +++ b/src/main/java/org/jabref/gui/theme/ThemeTypes.java @@ -3,18 +3,17 @@ import org.jabref.logic.l10n.Localization; 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/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java b/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java index d5f4fa0663f2..f0c8e362fc65 100644 --- a/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java +++ b/src/main/java/org/jabref/gui/undo/AbstractUndoableJabRefEdit.java @@ -1,9 +1,9 @@ package org.jabref.gui.undo; -import javax.swing.undo.AbstractUndoableEdit; - import org.jabref.logic.l10n.Localization; +import javax.swing.undo.AbstractUndoableEdit; + public class AbstractUndoableJabRefEdit extends AbstractUndoableEdit { @Override diff --git a/src/main/java/org/jabref/gui/undo/CountingUndoManager.java b/src/main/java/org/jabref/gui/undo/CountingUndoManager.java index 1e4eaa8981da..270ad3611faf 100644 --- a/src/main/java/org/jabref/gui/undo/CountingUndoManager.java +++ b/src/main/java/org/jabref/gui/undo/CountingUndoManager.java @@ -1,15 +1,15 @@ package org.jabref.gui.undo; -import javax.swing.undo.CannotUndoException; -import javax.swing.undo.UndoManager; -import javax.swing.undo.UndoableEdit; - import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; +import javax.swing.undo.CannotUndoException; +import javax.swing.undo.UndoManager; +import javax.swing.undo.UndoableEdit; + public class CountingUndoManager extends UndoManager { private int unchangedPoint; @@ -18,6 +18,7 @@ public class CountingUndoManager extends UndoManager { * 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); diff --git a/src/main/java/org/jabref/gui/undo/NamedCompound.java b/src/main/java/org/jabref/gui/undo/NamedCompound.java index 55be19a96e37..0d65b0851faa 100644 --- a/src/main/java/org/jabref/gui/undo/NamedCompound.java +++ b/src/main/java/org/jabref/gui/undo/NamedCompound.java @@ -1,10 +1,10 @@ package org.jabref.gui.undo; +import org.jabref.logic.l10n.Localization; + import javax.swing.undo.CompoundEdit; import javax.swing.undo.UndoableEdit; -import org.jabref.logic.l10n.Localization; - public class NamedCompound extends CompoundEdit { private final String name; @@ -27,12 +27,24 @@ public boolean hasEdits() { @Override public String getUndoPresentationName() { - return "" + Localization.lang("Undo") + ": " + name + "
        " + getPresentationName() + "
      "; + return "" + + Localization.lang("Undo") + + ": " + + name + + "
        " + + getPresentationName() + + "
      "; } @Override public String getRedoPresentationName() { - return "" + Localization.lang("Redo") + ": " + name + "
        " + getPresentationName() + "
      "; + return "" + + Localization.lang("Redo") + + ": " + + name + + "
        " + + getPresentationName() + + "
      "; } @Override diff --git a/src/main/java/org/jabref/gui/undo/RedoAction.java b/src/main/java/org/jabref/gui/undo/RedoAction.java index d5d3170a9933..1b9cc3bb429f 100644 --- a/src/main/java/org/jabref/gui/undo/RedoAction.java +++ b/src/main/java/org/jabref/gui/undo/RedoAction.java @@ -1,8 +1,6 @@ package org.jabref.gui.undo; -import java.util.function.Supplier; - -import javax.swing.undo.CannotRedoException; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import javafx.beans.binding.Bindings; @@ -12,7 +10,9 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.util.function.Supplier; + +import javax.swing.undo.CannotRedoException; /** * @implNote See also {@link UndoAction} @@ -22,12 +22,17 @@ public class RedoAction extends SimpleCommand { 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; - this.executable.bind(Bindings.and(needsDatabase(stateManager), undoManager.getRedoableProperty())); + this.executable.bind( + Bindings.and(needsDatabase(stateManager), undoManager.getRedoableProperty())); } @Override diff --git a/src/main/java/org/jabref/gui/undo/UndoAction.java b/src/main/java/org/jabref/gui/undo/UndoAction.java index 5eab905a9f8d..51efa93e0c5c 100644 --- a/src/main/java/org/jabref/gui/undo/UndoAction.java +++ b/src/main/java/org/jabref/gui/undo/UndoAction.java @@ -1,8 +1,6 @@ package org.jabref.gui.undo; -import java.util.function.Supplier; - -import javax.swing.undo.CannotUndoException; +import static org.jabref.gui.actions.ActionHelper.needsDatabase; import javafx.beans.binding.Bindings; @@ -12,7 +10,9 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.logic.l10n.Localization; -import static org.jabref.gui.actions.ActionHelper.needsDatabase; +import java.util.function.Supplier; + +import javax.swing.undo.CannotUndoException; /** * @implNote See also {@link RedoAction} @@ -22,12 +22,17 @@ public class UndoAction extends SimpleCommand { 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; - this.executable.bind(Bindings.and(needsDatabase(stateManager), undoManager.getUndoableProperty())); + this.executable.bind( + Bindings.and(needsDatabase(stateManager), undoManager.getUndoableProperty())); } @Override diff --git a/src/main/java/org/jabref/gui/undo/UndoableChangeType.java b/src/main/java/org/jabref/gui/undo/UndoableChangeType.java index 05d29f902e89..ce6999d68ffa 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableChangeType.java +++ b/src/main/java/org/jabref/gui/undo/UndoableChangeType.java @@ -16,7 +16,10 @@ public class UndoableChangeType extends AbstractUndoableJabRefEdit { 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) { @@ -27,7 +30,8 @@ public UndoableChangeType(BibEntry entry, EntryType oldType, EntryType newType) @Override public String getPresentationName() { - return Localization.lang("change type of entry %0 from %1 to %2", + return Localization.lang( + "change type of entry %0 from %1 to %2", StringUtil.boldHTML(entry.getCitationKey().orElse(Localization.lang("undefined"))), StringUtil.boldHTML(oldType.getDisplayName(), Localization.lang("undefined")), StringUtil.boldHTML(newType.getDisplayName())); diff --git a/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java b/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java index efb96c700206..37227ce7be80 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java +++ b/src/main/java/org/jabref/gui/undo/UndoableFieldChange.java @@ -5,7 +5,6 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +34,9 @@ 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"))); diff --git a/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java b/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java index 79803f1265d6..6f95a87d301b 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java +++ b/src/main/java/org/jabref/gui/undo/UndoableInsertEntries.java @@ -1,16 +1,15 @@ package org.jabref.gui.undo; -import java.util.Collections; -import java.util.List; - import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; +import java.util.List; + /** * This class represents the removal of entries. The constructor needs * references to the database, entries, and a boolean marked true if the undo @@ -43,8 +42,12 @@ public String getPresentationName() { 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")))); + return Localization.lang( + "paste entry %0", + StringUtil.boldHTML( + entries.getFirst() + .getCitationKey() + .orElse(Localization.lang("undefined")))); } else { return null; } @@ -52,8 +55,12 @@ public String getPresentationName() { 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")))); + return Localization.lang( + "insert entry %0", + StringUtil.boldHTML( + entries.getFirst() + .getCitationKey() + .orElse(Localization.lang("undefined")))); } else { return null; } diff --git a/src/main/java/org/jabref/gui/undo/UndoableInsertString.java b/src/main/java/org/jabref/gui/undo/UndoableInsertString.java index 6485861abbe1..0f0ef17a1082 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableInsertString.java +++ b/src/main/java/org/jabref/gui/undo/UndoableInsertString.java @@ -5,7 +5,6 @@ import org.jabref.model.database.KeyCollisionException; import org.jabref.model.entry.BibtexString; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java b/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java index 8b37869d6a89..343d603ef5c6 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java +++ b/src/main/java/org/jabref/gui/undo/UndoableKeyChange.java @@ -28,7 +28,8 @@ public UndoableKeyChange(BibEntry entry, String oldValue, String newValue) { @Override public String getPresentationName() { - return Localization.lang("change key from %0 to %1", + return Localization.lang( + "change key from %0 to %1", StringUtil.boldHTML(oldValue, Localization.lang("undefined")), StringUtil.boldHTML(newValue, Localization.lang("undefined"))); } diff --git a/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java b/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java index 10d89179eaf0..af90af87ebda 100644 --- a/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java +++ b/src/main/java/org/jabref/gui/undo/UndoablePreambleChange.java @@ -12,8 +12,7 @@ public class UndoablePreambleChange extends AbstractUndoableJabRefEdit { 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; diff --git a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java index 133c44a81dda..75c806a240bc 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java +++ b/src/main/java/org/jabref/gui/undo/UndoableRemoveEntries.java @@ -1,17 +1,16 @@ package org.jabref.gui.undo; -import java.util.Collections; -import java.util.List; - import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.EntriesEventSource; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; +import java.util.List; + /** * This class represents the removal of entries. The constructor needs * references to the database, the entries, and the map of open entry editors. @@ -46,8 +45,12 @@ public String getPresentationName() { 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")))); + return Localization.lang( + "cut entry %0", + StringUtil.boldHTML( + entries.getFirst() + .getCitationKey() + .orElse(Localization.lang("undefined")))); } else { return null; } @@ -55,8 +58,12 @@ public String getPresentationName() { 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")))); + return Localization.lang( + "remove entry %0", + StringUtil.boldHTML( + entries.getFirst() + .getCitationKey() + .orElse(Localization.lang("undefined")))); } else { return null; } diff --git a/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java b/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java index 2c086ed3cf0a..cca522be9c3e 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java +++ b/src/main/java/org/jabref/gui/undo/UndoableRemoveString.java @@ -5,7 +5,6 @@ import org.jabref.model.database.KeyCollisionException; import org.jabref.model.entry.BibtexString; import org.jabref.model.strings.StringUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/jabref/gui/undo/UndoableStringChange.java b/src/main/java/org/jabref/gui/undo/UndoableStringChange.java index 883f16757506..063f402591a5 100644 --- a/src/main/java/org/jabref/gui/undo/UndoableStringChange.java +++ b/src/main/java/org/jabref/gui/undo/UndoableStringChange.java @@ -11,7 +11,8 @@ public class UndoableStringChange extends AbstractUndoableJabRefEdit { private final String newValue; private final boolean nameChange; - public UndoableStringChange(BibtexString string, boolean nameChange, String oldValue, String newValue) { + public UndoableStringChange( + BibtexString string, boolean nameChange, String oldValue, String newValue) { this.string = string; this.oldValue = oldValue; this.newValue = newValue; @@ -20,9 +21,12 @@ 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", + 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)); } diff --git a/src/main/java/org/jabref/gui/util/BaseDialog.java b/src/main/java/org/jabref/gui/util/BaseDialog.java index 3e2d4c4c1822..a583556558ed 100644 --- a/src/main/java/org/jabref/gui/util/BaseDialog.java +++ b/src/main/java/org/jabref/gui/util/BaseDialog.java @@ -1,6 +1,6 @@ package org.jabref.gui.util; -import java.util.Optional; +import com.airhacks.afterburner.injection.Injector; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; @@ -13,27 +13,33 @@ import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; -import com.airhacks.afterburner.injection.Injector; +import java.util.Optional; public class BaseDialog extends Dialog { protected BaseDialog() { - getDialogPane().getScene().setOnKeyPressed(event -> { - KeyBindingRepository keyBindingRepository = Injector.instantiateModelOrService(KeyBindingRepository.class); - if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.CLOSE, event)) { - close(); - } else if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.DEFAULT_DIALOG_ACTION, event)) { - getDefaultButton().ifPresent(Button::fire); - } - - // all buttons in base dialogs react on enter - if (event.getCode() == KeyCode.ENTER) { - if (event.getTarget() instanceof Button) { - ((Button) event.getTarget()).fire(); - event.consume(); - } - } - }); + getDialogPane() + .getScene() + .setOnKeyPressed( + event -> { + KeyBindingRepository keyBindingRepository = + Injector.instantiateModelOrService(KeyBindingRepository.class); + if (keyBindingRepository.checkKeyCombinationEquality( + KeyBinding.CLOSE, event)) { + close(); + } else if (keyBindingRepository.checkKeyCombinationEquality( + KeyBinding.DEFAULT_DIALOG_ACTION, event)) { + getDefaultButton().ifPresent(Button::fire); + } + + // all buttons in base dialogs react on enter + if (event.getCode() == KeyCode.ENTER) { + if (event.getTarget() instanceof Button) { + ((Button) event.getTarget()).fire(); + event.consume(); + } + } + }); setDialogIcon(IconTheme.getJabRefImage()); setResizable(true); @@ -45,9 +51,9 @@ private Optional