From 190a5cd52e274493e046c9983a03d7422134a365 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 20 Oct 2017 16:35:07 +0800 Subject: [PATCH 1/7] Small fix in drag and drop handler of linked files (#3328) --- src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index 7bb10d7b38a..2f47c572b52 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -86,6 +86,7 @@ private void setUpFilesDragAndDrop() { if (dragboard.hasFiles()) { List linkedFiles = dragboard.getFiles().stream().map(File::toPath).map(viewModel::fromFile).collect(Collectors.toList()); items.addAll(linkedFiles); + success = true; } event.setDropCompleted(success); event.consume(); From 384915a011d2a751b2785a657be12edcea50d013 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 20 Oct 2017 17:16:00 +0800 Subject: [PATCH 2/7] Fix "path not found" exception --- .../jabref/logic/util/io/CiteKeyBasedFileFinder.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/logic/util/io/CiteKeyBasedFileFinder.java b/src/main/java/org/jabref/logic/util/io/CiteKeyBasedFileFinder.java index 38b26da8c0d..462250d88f1 100644 --- a/src/main/java/org/jabref/logic/util/io/CiteKeyBasedFileFinder.java +++ b/src/main/java/org/jabref/logic/util/io/CiteKeyBasedFileFinder.java @@ -88,10 +88,12 @@ public Set findFilesByExtension(List directories, List exten Set result = new HashSet<>(); for (Path directory : directories) { - try (Stream files = Files.find(directory, Integer.MAX_VALUE, isFileWithCorrectExtension)) { - result.addAll(files.collect(Collectors.toSet())); - } catch (IOException e) { - LOGGER.error("Problem in finding files", e); + if (Files.exists(directory)) { + try (Stream files = Files.find(directory, Integer.MAX_VALUE, isFileWithCorrectExtension)) { + result.addAll(files.collect(Collectors.toSet())); + } catch (IOException e) { + LOGGER.error("Problem in finding files", e); + } } } return result; From ffab9946c5159899edfb3982261bac9344c4cdf1 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 20 Oct 2017 17:16:13 +0800 Subject: [PATCH 3/7] Small code cleanup --- .../gui/actions/CopyBibTeXKeyAndLinkAction.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java b/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java index 29d61d68c88..d5b1652b6c4 100644 --- a/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java +++ b/src/main/java/org/jabref/gui/actions/CopyBibTeXKeyAndLinkAction.java @@ -3,12 +3,12 @@ import java.util.List; import java.util.stream.Collectors; -import javafx.application.Platform; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import org.jabref.JabRefGUI; import org.jabref.gui.maintable.MainTable; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.OS; import org.jabref.model.entry.BibEntry; @@ -48,14 +48,11 @@ public void action() throws Exception { } // This works on Mac and Windows 10, but not on Ubuntu 16.04 - Platform.runLater(new Runnable() { - @Override - public void run() { - final Clipboard clipboard = Clipboard.getSystemClipboard(); - final ClipboardContent content = new ClipboardContent(); - content.putHtml(sb.toString()); - clipboard.setContent(content); - } + DefaultTaskExecutor.runInJavaFXThread(() -> { + final Clipboard clipboard = Clipboard.getSystemClipboard(); + final ClipboardContent content = new ClipboardContent(); + content.putHtml(sb.toString()); + clipboard.setContent(content); }); int copied = entriesWithKey.size(); From 1df1612e424bcbe23c2bad549e6c2c3e30e16beb Mon Sep 17 00:00:00 2001 From: Patrick Scheibe Date: Fri, 20 Oct 2017 19:52:37 +0200 Subject: [PATCH 4/7] Fix location bundle with fast access (#3327) * Made the LocalizationBundle a private class of Localization and ensured, that all accesses to it are only lookups in hashmaps Made all access of Location-messages a fast hash table lookup. Fixed one message in JabRefFrame that used an escaped key instead of the unescaped version. * Fix NPE in MainTable (#3318) * Fix NPE in MainTable * Fix build * Use of HashMap instead of Hashtable General code cleanup * Forgot to replace one null test * Reordering of import statements and fields Replacement of last null test with Objects.requireNonNull --- src/main/java/org/jabref/JabRefMain.java | 4 +- src/main/java/org/jabref/gui/JabRefFrame.java | 4 +- .../org/jabref/logic/l10n/Localization.java | 191 ++++++++++++++---- .../jabref/logic/l10n/LocalizationBundle.java | 36 ---- .../jabref/preferences/JabRefPreferences.java | 6 + 5 files changed, 162 insertions(+), 79 deletions(-) delete mode 100644 src/main/java/org/jabref/logic/l10n/LocalizationBundle.java diff --git a/src/main/java/org/jabref/JabRefMain.java b/src/main/java/org/jabref/JabRefMain.java index e57f22c69b6..be386b1dc8c 100644 --- a/src/main/java/org/jabref/JabRefMain.java +++ b/src/main/java/org/jabref/JabRefMain.java @@ -66,7 +66,9 @@ private static void start(String[] args) { Globals.prefs = preferences; Globals.startBackgroundTasks(); - Localization.setLanguage(preferences.get(JabRefPreferences.LANGUAGE)); + + // Note that the language was already set during the initialization of the preferences and it is safe to + // call the next function. Globals.prefs.setLanguageDependentDefaultValues(); // Perform Migrations diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 62465bdc147..13d1a83366b 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -254,8 +254,8 @@ public class JabRefFrame extends JFrame implements OutputPrinter { Globals.getKeyPrefs().getKey(KeyBinding.OPEN_CONSOLE), IconTheme.JabRefIcon.CONSOLE.getIcon()); private final AbstractAction pullChangesFromSharedDatabase = new GeneralAction(Actions.PULL_CHANGES_FROM_SHARED_DATABASE, - Localization.menuTitle("Pull_changes_from_shared_database"), - Localization.lang("Pull_changes_from_shared_database"), + Localization.menuTitle("Pull changes from shared database"), + Localization.lang("Pull changes from shared database"), Globals.getKeyPrefs().getKey(KeyBinding.PULL_CHANGES_FROM_SHARED_DATABASE), IconTheme.JabRefIcon.PULL.getIcon()); private final AbstractAction mark = new GeneralAction(Actions.MARK_ENTRIES, Localization.menuTitle("Mark entries"), diff --git a/src/main/java/org/jabref/logic/l10n/Localization.java b/src/main/java/org/jabref/logic/l10n/Localization.java index 8fafc13b3ae..8e5ef7982d8 100644 --- a/src/main/java/org/jabref/logic/l10n/Localization.java +++ b/src/main/java/org/jabref/logic/l10n/Localization.java @@ -1,41 +1,103 @@ package org.jabref.logic.l10n; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; import java.util.Locale; import java.util.MissingResourceException; import java.util.Objects; import java.util.Optional; import java.util.ResourceBundle; +import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +/** + * Provides handling for messages and menu entries in the preferred language of the user. + *

+ * Notes: All messages and menu-entries in JabRef are stored in escaped form like "This_is_a_message". This message + * serves as key inside the {@link l10n} properties files that hold the translation for many languages. When a message + * is accessed, it needs to be unescaped and possible parameters that can appear in a message need to be filled with + * values. + *

+ * This implementation loads the appropriate language by importing all keys/values from the correct bundle and stores + * them in unescaped form inside a {@link LocalizationBundle} which provides fast access because it caches the key-value + * pairs. + *

+ * The access to this is given by the functions {@link Localization#lang(String, String...)} and {@link + * Localization#menuTitle(String, String...)} that developers should use whenever they use strings for the e.g. GUI that + * need to be translatable. + */ public class Localization { - public static final String RESOURCE_PREFIX = "l10n/JabRef"; - public static final String MENU_RESOURCE_PREFIX = "l10n/Menu"; public static final String BIBTEX = "BibTeX"; + static final String RESOURCE_PREFIX = "l10n/JabRef"; + static final String MENU_RESOURCE_PREFIX = "l10n/Menu"; private static final Log LOGGER = LogFactory.getLog(Localization.class); - private static ResourceBundle messages; - private static ResourceBundle menuTitles; + private static Locale locale; + private static LocalizationBundle localizedMessages; + private static LocalizationBundle localizedMenuTitles; private Localization() { } - public static LocalizationBundle getMessages() { - return new LocalizationBundle(messages); + /** + * Public access to all messages that are not menu-entries + * + * @param key The key of the message in unescaped form like "All fields" + * @param params Replacement strings for parameters %0, %1, etc. + * @return The message with replaced parameters + */ + public static String lang(String key, String... params) { + if (localizedMessages == null) { + // I'm logging this because it should never happen + LOGGER.error("Messages are not initialized."); + setLanguage("en"); + } + return lookup(localizedMessages, "message", key, params); + } + + /** + * Public access to menu entry messages + * + * @param key The key of the message in unescaped form like "Save all" + * @param params Replacement strings for parameters %0, %1, etc. + * @return The message with replaced parameters + */ + public static String menuTitle(String key, String... params) { + if (localizedMenuTitles == null) { + // I'm logging this because it should never happen + LOGGER.error("Menu entries are not initialized"); + setLanguage("en"); + } + return lookup(localizedMenuTitles, "menu item", key, params); } + /** + * Sets the language and loads the appropriate translations. Note, that this function should be called before any + * other function of this class. + * + * @param language Language identifier like "en", "de", etc. + */ public static void setLanguage(String language) { Optional knownLanguage = Languages.convertToSupportedLocale(language); + final Locale defaultLocale = Locale.getDefault(); if (!knownLanguage.isPresent()) { - LOGGER.warn("Language " + language + " is not supported by JabRef (Default:" + Locale.getDefault() + ")"); + LOGGER.warn("Language " + language + " is not supported by JabRef (Default:" + defaultLocale + ")"); setLanguage("en"); return; } - - Locale locale = knownLanguage.get(); + // avoid reinitialization of the language bundles + final Locale langLocale = knownLanguage.get(); + if ((locale != null) && locale.equals(langLocale) && locale.equals(defaultLocale)) { + return; + } + locale = langLocale; Locale.setDefault(locale); javax.swing.JComponent.setDefaultLocale(locale); @@ -48,54 +110,103 @@ public static void setLanguage(String language) { } } + /** + * Public access to the messages bundle for classes like AbstractView. + * + * @return The internally cashed bundle. + */ + public static LocalizationBundle getMessages() { + // avoid situations where this function is called before any language was set + if (locale == null) { + setLanguage("en"); + } + return localizedMessages; + } + + /** + * Creates and caches the language bundles used in JabRef for a particular language. This function first loads + * correct version of the "escaped" bundles that are given in {@link l10n}. After that, it stores the unescaped + * version in a cached {@link LocalizationBundle} for fast access. + * + * @param locale Localization to use. + */ private static void createResourceBundles(Locale locale) { - messages = ResourceBundle.getBundle(RESOURCE_PREFIX, locale, new EncodingControl(StandardCharsets.UTF_8)); - menuTitles = ResourceBundle.getBundle(MENU_RESOURCE_PREFIX, locale, new EncodingControl(StandardCharsets.UTF_8)); + ResourceBundle messages = ResourceBundle.getBundle(RESOURCE_PREFIX, locale, new EncodingControl(StandardCharsets.UTF_8)); + ResourceBundle menuTitles = ResourceBundle.getBundle(MENU_RESOURCE_PREFIX, locale, new EncodingControl(StandardCharsets.UTF_8)); + Objects.requireNonNull(messages, "Could not load " + RESOURCE_PREFIX + " resource."); + Objects.requireNonNull(menuTitles, "Could not load " + MENU_RESOURCE_PREFIX + " resource."); + localizedMessages = new LocalizationBundle(createLookupMap(messages)); + localizedMenuTitles = new LocalizationBundle(createLookupMap(menuTitles)); } /** - * In the translation, %0, ..., %9 is replaced by the respective params given + * Helper function to create a HashMap from the key/value pairs of a bundle. * - * @param resBundle the ResourceBundle to use - * @param idForErrorMessage output when translation is not found - * @param key the key to lookup in resBundle - * @param params a list of Strings to replace %0, %1, ... - * @return + * @param baseBundle JabRef language bundle with keys and values for translations. + * @return Lookup map for the baseBundle. */ - protected static String translate(ResourceBundle resBundle, String idForErrorMessage, String key, String... params) { - Objects.requireNonNull(resBundle); + private static HashMap createLookupMap(ResourceBundle baseBundle) { + final ArrayList baseKeys = Collections.list(baseBundle.getKeys()); + return new HashMap<>(baseKeys.stream().collect( + Collectors.toMap( + key -> new LocalizationKey(key).getTranslationValue(), + key -> new LocalizationKey(baseBundle.getString(key)).getTranslationValue()) + )); + } - String translation = null; - try { - String propertiesKey = new LocalizationKey(key).getPropertiesKeyUnescaped(); - translation = resBundle.getString(propertiesKey); - } catch (MissingResourceException ex) { + /** + * This looks up a key in the bundle and replaces parameters %0, ..., %9 with the respective params given. Note that + * the keys are the "unescaped" strings from the bundle property files. + * + * @param bundle The {@link LocalizationBundle} which means either {@link Localization#localizedMenuTitles} + * or {@link Localization#localizedMessages}. + * @param idForErrorMessage Identifier-string when the translation is not found. + * @param key The lookup key. + * @param params The parameters that should be inserted into the message + * @return The final message with replaced parameters. + */ + private static String lookup(LocalizationBundle bundle, String idForErrorMessage, String key, String... params) { + Objects.requireNonNull(key); + + String translation = bundle.containsKey(key) ? bundle.getString(key) : ""; + if (translation.isEmpty()) { LOGGER.warn("Warning: could not get " + idForErrorMessage + " translation for \"" + key + "\" for locale " + Locale.getDefault()); - } - if ((translation == null) || translation.isEmpty()) { - LOGGER.warn("Warning: no " + idForErrorMessage + " translation for \"" + key + "\" for locale " - + Locale.getDefault()); - translation = key; } - return new LocalizationKeyParams(translation, params).replacePlaceholders(); } - public static String lang(String key, String... params) { - if (messages == null) { - setLanguage("en"); + /** + * A bundle for caching localized strings. Needed to support JavaFX inline binding. + */ + private static class LocalizationBundle extends ResourceBundle { + + private final HashMap lookup; + + LocalizationBundle(HashMap lookupMap) { + lookup = lookupMap; } - return translate(messages, "message", key, params); - } - public static String menuTitle(String key, String... params) { - if (menuTitles == null) { - setLanguage("en"); + public final Object handleGetObject(String key) { + Objects.requireNonNull(key); + return lookup.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(lookup.keySet()); } - return translate(menuTitles, "menu item", key, params); - } + @Override + protected Set handleKeySet() { + return lookup.keySet(); + } + + @Override + public boolean containsKey(String key) { + return (key != null) && lookup.containsKey(key); + } + } } diff --git a/src/main/java/org/jabref/logic/l10n/LocalizationBundle.java b/src/main/java/org/jabref/logic/l10n/LocalizationBundle.java deleted file mode 100644 index 35250cf6e06..00000000000 --- a/src/main/java/org/jabref/logic/l10n/LocalizationBundle.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jabref.logic.l10n; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Objects; -import java.util.ResourceBundle; -import java.util.stream.Collectors; - -/** - * A bundle containing localized strings. - * It wraps an ordinary resource bundle and performs escaping/unescaping of keys and values similar to - * {@link Localization}. Needed to support JavaFX inline binding. - */ -public class LocalizationBundle extends ResourceBundle { - - private final ResourceBundle baseBundle; - - public LocalizationBundle(ResourceBundle baseBundle) { - this.baseBundle = Objects.requireNonNull(baseBundle); - } - - @Override - protected Object handleGetObject(String key) { - return Localization.translate(baseBundle, "message", key); - } - - @Override - public Enumeration getKeys() { - ArrayList baseKeys = Collections.list(baseBundle.getKeys()); - List unescapedKeys = baseKeys.stream().map(key -> new LocalizationKey(key).getTranslationValue()) - .collect(Collectors.toList()); - return Collections.enumeration(unescapedKeys); - } -} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index b225120cb9c..6bbd42f0710 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -447,6 +447,12 @@ private JabRefPreferences() { // load user preferences prefs = Preferences.userNodeForPackage(PREFS_BASE_CLASS); + // Since some of the preference settings themselves use localized strings, we cannot set the language after + // the initialization of the preferences in main + // Otherwise that language framework will be instantiated and more importantly, statically initialized preferences + // like the SearchDisplayMode will never be translated. + Localization.setLanguage(prefs.get(LANGUAGE, "en")); + SearchPreferences.putDefaults(defaults); defaults.put(TEXMAKER_PATH, JabRefDesktop.getNativeDesktop().detectProgramPath("texmaker", "Texmaker")); From a7d40f1d8ff7586dea8cf8d92add2402483209d4 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 21 Oct 2017 11:59:38 +0800 Subject: [PATCH 5/7] Change integrity message for names depending on database mode (#3330) --- .../java/org/jabref/logic/integrity/FieldCheckers.java | 2 +- .../org/jabref/logic/integrity/PersonNamesChecker.java | 10 +++++++++- src/main/resources/l10n/JabRef_da.properties | 2 +- src/main/resources/l10n/JabRef_de.properties | 2 +- src/main/resources/l10n/JabRef_el.properties | 2 +- src/main/resources/l10n/JabRef_en.properties | 2 +- src/main/resources/l10n/JabRef_es.properties | 2 +- src/main/resources/l10n/JabRef_fa.properties | 2 +- src/main/resources/l10n/JabRef_fr.properties | 2 +- src/main/resources/l10n/JabRef_in.properties | 2 +- src/main/resources/l10n/JabRef_it.properties | 2 +- src/main/resources/l10n/JabRef_ja.properties | 2 +- src/main/resources/l10n/JabRef_nl.properties | 2 +- src/main/resources/l10n/JabRef_no.properties | 2 +- src/main/resources/l10n/JabRef_pt_BR.properties | 2 +- src/main/resources/l10n/JabRef_ru.properties | 2 +- src/main/resources/l10n/JabRef_sv.properties | 2 +- src/main/resources/l10n/JabRef_tr.properties | 2 +- src/main/resources/l10n/JabRef_vi.properties | 2 +- src/main/resources/l10n/JabRef_zh.properties | 2 +- .../jabref/logic/integrity/PersonNamesCheckerTest.java | 9 +++++++-- 21 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/jabref/logic/integrity/FieldCheckers.java b/src/main/java/org/jabref/logic/integrity/FieldCheckers.java index 5439e09b7ef..29e4cbe5601 100644 --- a/src/main/java/org/jabref/logic/integrity/FieldCheckers.java +++ b/src/main/java/org/jabref/logic/integrity/FieldCheckers.java @@ -30,7 +30,7 @@ private static Multimap getAllMap(BibDatabaseContext datab fieldCheckers.put(field, new AbbreviationChecker()); } for (String field : InternalBibtexFields.getPersonNameFields()) { - fieldCheckers.put(field, new PersonNamesChecker()); + fieldCheckers.put(field, new PersonNamesChecker(databaseContext)); } fieldCheckers.put(FieldName.BOOKTITLE, new BooktitleChecker()); fieldCheckers.put(FieldName.TITLE, new BracketChecker()); diff --git a/src/main/java/org/jabref/logic/integrity/PersonNamesChecker.java b/src/main/java/org/jabref/logic/integrity/PersonNamesChecker.java index 33ee505871a..cbb542ed54b 100644 --- a/src/main/java/org/jabref/logic/integrity/PersonNamesChecker.java +++ b/src/main/java/org/jabref/logic/integrity/PersonNamesChecker.java @@ -4,11 +4,19 @@ import java.util.Optional; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.AuthorList; import org.jabref.model.strings.StringUtil; public class PersonNamesChecker implements ValueChecker { + private final BibDatabaseMode bibMode; + + public PersonNamesChecker(BibDatabaseContext databaseContext) { + this.bibMode = databaseContext.getMode(); + } + @Override public Optional checkValue(String value) { if (StringUtil.isBlank(value)) { @@ -28,7 +36,7 @@ public Optional checkValue(String value) { AuthorList authorList = AuthorList.parse(value); if (!authorList.getAsLastFirstNamesWithAnd(false).equals(value) && !authorList.getAsFirstLastNamesWithAnd().equals(value)) { - return Optional.of(Localization.lang("Names are not in the standard BibTeX format.")); + return Optional.of(Localization.lang("Names are not in the standard %0 format.", bibMode.getFormattedName())); } return Optional.empty(); diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index e9a5b0f8fd6..ab05c46caa4 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 063f66546f6..180d5d64c11 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics=Teile_anonyme_Statistiken Telemetry\:_Help_make_JabRef_better=Telemetrie\:_Helfen_Sie,_JabRef_zu_verbessern To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.=Zur_Verbesserung_der_Benutzererfahrung_würden_wir_gerne_anonyme_Statistiken_über_die_Features_sammeln,_die_Sie_benutzen._Wir_zeichnen_nur_auf,_welche_Features_Sie_nutzen_und_wie_oft_Sie_diese_benutzen._Es_werden_keinerlei_persönliche_Informationen_über_Sie_oder_den_Inhalt_Ihrer_Bibliografieeinträge_gesammelt._Die_einmal_erlaubte_Datenaufzeichnnung_kann_jederzeit_unter_Optionen->Einstellungen->Allgemein_deaktiviert_werden. This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?=Diese_Datei_wurde_automatisch_gefunden._Möchten_Sie_sie_dem_Eintrag_zuordnen? -Names_are_not_in_the_standard_BibTeX_format.=Namen_entsprechen_nicht_dem_Standard_BibTeX-Format +Names_are_not_in_the_standard_%0_format.=Namen_entsprechen_nicht_dem_Standard_%0-Format Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.=Die_ausgewählte_Datei_unwiderruflich_löschen_oder_nur_vom_Eintrag_entfernen?_Drücken_sie_Entfernen,_um_die_Datei_von_der_Festplatte_zu_löschen. Delete_'%0'=Lösche_Datei_'%0' diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index e124ca23ea1..9f0c30a79c7 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 0f77686655f..11b7b454ee1 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics=Share_anonymous_statistics Telemetry\:_Help_make_JabRef_better=Telemetry\:_Help_make_JabRef_better To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.=To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General. This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?=This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry? -Names_are_not_in_the_standard_BibTeX_format.=Names_are_not_in_the_standard_BibTeX_format. +Names_are_not_in_the_standard_%0_format.=Names_are_not_in_the_standard_%0_format. Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.=Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk. Delete_'%0'=Delete_'%0' diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 7cc76b81cbe..f5fdde0b827 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics=Compartir_estadísticas_anónimas Telemetry\:_Help_make_JabRef_better=Telemetría:\_Ayude_a_mejorar_JabRef To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.=Para_mejorar_la_experiencia_de_usuario,_nos_gustaría_recopilar_estadísitcas_anónimas_sobre_las_funcionalidades_de_JabRef_que_usa._Sólo_se_registrará_qué_funcionalidades_emplea_y_con_qué_frecuencia._No_se_recopilará_ningún_dato_personal_ni_el_contenido_de_los_elementos_bibliográficos._Si_decide_permitir_la_recopilación_de_datos,_podrá_deshabilitarlo_posteriormente_en_Opciones_->_Preferencias_->General This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index c795f665acb..081f667b150 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index c5171a5e41d..233366ca516 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics=Partager_anonymement_les_statistiques Telemetry\:_Help_make_JabRef_better=Télémétrie_:_contribue_à_l'amélioration_de_JabRef To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.=Pour_améliorer_l'ergonomie_pour_l'utilisateur,_nous_souhaiterions_collecter_des_statistiques_anonymisées_sur_les_fonctionnalité_que_vous_utilisez._Nous_n'enregistrerons_que_les_fonctionnalités_que_vous_utilisés_et_leur_fréquence_d'utiliations._Nous_n'enregistrerons_jamais_des_données_personneles_ni_le_contenu_de_vos_entrées_bibliographiques._Si_vous_choisissez_d'autoriser_la_collecte_des_données,_vous_pourrez_plus_tard_l'interdire_via_Options_->_Préférences_->_Géneral. This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?=Ce_fichier_a_été_trouvé_automatiquement._Voulez-vous_le_lier_à_cette_entrée_? -Names_are_not_in_the_standard_BibTeX_format.=Des_noms_ne_sont_pas_au_standard_du_format_BibTeX. +Names_are_not_in_the_standard_%0_format.=Des_noms_ne_sont_pas_au_standard_du_format_%0. Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.=Supprimer_définitivement_le_ficher_sélectionné_du_disque,_ou_effacer_simplement_le_fichier_de_cette_entrée_?_Presser_Suppr_supprimera_définitivement_ce_fichier_du_disque. Delete_'%0'=Supprimer_'%0' diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index b2ca29b125e..312eb8e2a24 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 63c96b4e389..9ee5455765c 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics=Condividi_statistiche_anonime Telemetry\:_Help_make_JabRef_better=Telemetria\:_Aiuta_a_migliorare_JabRef To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.=Per_migliorare_l'esperienza_utente,_vorremmo_raccogliere_delle_statistiche_anonime_sulle_funzioni_che_usi._Registreremo_solo_quali_funzioni_accedi_e_quanto_spesso._Non_registreremo_dati_personali_né_il_contenuto_delle_voci_bibliografiche._Se_scegli_di_permettere_la_raccolta_dei_dati,_potrai_disabilitarla_successivamente_da_Opzioni_->_Preferenze_->_Generale. This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?=Questo_file_è_stato_trovato_automaticamente._Vuoi_collegarlo_a_questa_voce? -Names_are_not_in_the_standard_BibTeX_format.=I_nomi_non_sono_nel_formato_standard_BibTeX. +Names_are_not_in_the_standard_%0_format.=I_nomi_non_sono_nel_formato_standard_%0. Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.=Cancello_permanentemente_i_file_selezionati_dal_disco,_o_rimuovo_solo_il_file_dalla_voce?_Premendo_Cancella_il_file_verrà_cancellato_permanentemente_dal_disco. Delete_'%0'=Cancella_'%0' diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 455d25bfcc5..ae7215e4608 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics=匿名統計を共有 Telemetry\:_Help_make_JabRef_better=遠隔計測:JabRefの改善に貢献 To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.=ユーザー・エクスペリエンスを改善するために,あなたが使用する機能の匿名統計を収集したいと考えています.あなたがどの機能にアクセスし,それをどれくらい頻繁に使ったかのみを記録します.個人的データや文献項目の内容は収集しません.データ収集を許可した場合でも,オプション→設定→一般から後で無効にすることができます. This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?=このファイルは自動的に検出されました.ファイルをこの項目にリンクしますか? -Names_are_not_in_the_standard_BibTeX_format.=名称がBibTeX標準形式ではありません. +Names_are_not_in_the_standard_%0_format.=名称が%0標準形式ではありません. Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.=選択したファイルをディスクから永久に削除しますか,それとも項目からファイルを除去するだけにしますか?削除を押すとディスクからファイルを永久に削除することになります. Delete_'%0'=「%0」を削除 diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 5e24b560d2b..e5dc78ab445 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 731da5678c1..4b2cc134b20 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index db82482b610..39bf9ad03a4 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index b89d88b5c56..08b1b952819 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index b4fc40977b5..ee2d06ca94e 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 56d67ca94c1..494bd4c7ae3 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics=Anonim_istatistikleri_paylaş Telemetry\:_Help_make_JabRef_better=Telemetri\:JabRef'i_daha_iyi_yapmaya_yardım_et To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.=Kullanıcı_deneyimini_geliştirmek_için,_kullandığınız_özelliklerle_ilgili_anonim_istatistikleri_toplamak_istiyoruz._Yalnızca_kullandığınız_özellikleri_ve_kullanma_sıklığını_kaydedeceğiz._Kişisel_verileri_ya_da_bibliyografik_ögelerin_içeriğini_toplamayacağız._Eğer_veri_toplamaya_izin_vermeyi_seçerseniz,_bunu_daha_sonra_Seçenekler_->_Tercihler_aracılığıyla_etkisizleştirebilirsiniz_- This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?=Bu_dosya_otomatik_olarak_bulundu._Bu_girdiye_bağlantılamak_ister_misiniz? -Names_are_not_in_the_standard_BibTeX_format.=İsimler,_standart_BibTeX_biçeminde_değil. +Names_are_not_in_the_standard_%0_format.=İsimler,_standart_%0_biçeminde_değil. Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.=Seçili_dosyayı_diskten_kalıcı_olarak_sil,_ya_da_sadece_dosyayı_girdiden_kaldır?_Sile_basmak_dosyayı_diskten_kalıcı_olarak_silecek. Delete_'%0'='%0'_Sil diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index db21846949b..6caad8a18a7 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index 90df7c50edb..db3f0a04710 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -2329,7 +2329,7 @@ Share_anonymous_statistics= Telemetry\:_Help_make_JabRef_better= To_improve_the_user_experience,_we_would_like_to_collect_anonymous_statistics_on_the_features_you_use._We_will_only_record_what_features_you_access_and_how_often_you_do_it._We_will_neither_collect_any_personal_data_nor_the_content_of_bibliographic_items._If_you_choose_to_allow_data_collection,_you_can_later_disable_it_via_Options_->_Preferences_->_General.= This_file_was_found_automatically._Do_you_want_to_link_it_to_this_entry?= -Names_are_not_in_the_standard_BibTeX_format.= +Names_are_not_in_the_standard_%0_format.= Delete_the_selected_file_permanently_from_disk,_or_just_remove_the_file_from_the_entry?_Pressing_Delete_will_delete_the_file_permanently_from_disk.= Delete_'%0'= diff --git a/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java b/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java index 05aedb50461..2f603389978 100644 --- a/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java +++ b/src/test/java/org/jabref/logic/integrity/PersonNamesCheckerTest.java @@ -2,6 +2,9 @@ import java.util.Optional; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; + import org.junit.Before; import org.junit.Test; @@ -9,11 +12,13 @@ public class PersonNamesCheckerTest { - PersonNamesChecker checker; + private PersonNamesChecker checker; @Before public void setUp() throws Exception { - checker = new PersonNamesChecker(); + BibDatabaseContext databaseContext = new BibDatabaseContext(); + databaseContext.setMode(BibDatabaseMode.BIBTEX); + checker = new PersonNamesChecker(databaseContext); } @Test From a7bf676f31aba7269d3ae4e559f1fc67a49f61b6 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sat, 21 Oct 2017 19:38:45 +0800 Subject: [PATCH 6/7] Fix #3292: annotations are now automatically refreshed (#3325) * Fix NPE * Fix reload of file annotations * Refactor file monitor to use nio WatchService * Watch pdf files for change and remove refresh button * Fix build * Revert changes to wrongly pushed test data * Update DatabaseChangeMonitor.java * Update BasePanel.java --- CHANGELOG.md | 1 + src/main/java/org/jabref/Globals.java | 2 +- .../jabref/collab/ChangeDisplayDialog.java | 2 +- .../java/org/jabref/collab/ChangeScanner.java | 7 +- .../jabref/collab/DatabaseChangeMonitor.java | 161 +++++++++++++ .../org/jabref/collab/FileUpdateMonitor.java | 221 ------------------ .../org/jabref/collab/FileUpdatePanel.java | 2 +- .../java/org/jabref/gui/AbstractView.java | 3 +- src/main/java/org/jabref/gui/BasePanel.java | 107 ++------- .../java/org/jabref/gui/DefaultInjector.java | 3 + .../fileannotationtab/FileAnnotationTab.fxml | 8 - .../FileAnnotationTabController.java | 13 +- .../FileAnnotationTabViewModel.java | 47 +++- .../gui/exporter/SaveDatabaseAction.java | 23 +- .../util}/FileUpdateListener.java | 8 +- .../jabref/gui/util/FileUpdateMonitor.java | 88 +++++++ .../logic/pdf/EntryAnnotationImporter.java | 7 +- .../jabref/logic/pdf/FileAnnotationCache.java | 9 +- src/main/resources/l10n/JabRef_da.properties | 2 - src/main/resources/l10n/JabRef_de.properties | 2 - src/main/resources/l10n/JabRef_el.properties | 2 - src/main/resources/l10n/JabRef_en.properties | 2 - src/main/resources/l10n/JabRef_es.properties | 2 - src/main/resources/l10n/JabRef_fa.properties | 2 - src/main/resources/l10n/JabRef_fr.properties | 2 - src/main/resources/l10n/JabRef_in.properties | 2 - src/main/resources/l10n/JabRef_it.properties | 2 - src/main/resources/l10n/JabRef_ja.properties | 2 - src/main/resources/l10n/JabRef_nl.properties | 2 - src/main/resources/l10n/JabRef_no.properties | 2 - .../resources/l10n/JabRef_pt_BR.properties | 2 - src/main/resources/l10n/JabRef_ru.properties | 2 - src/main/resources/l10n/JabRef_sv.properties | 2 - src/main/resources/l10n/JabRef_tr.properties | 2 - src/main/resources/l10n/JabRef_vi.properties | 2 - src/main/resources/l10n/JabRef_zh.properties | 2 - .../pdf/EntryAnnotationImporterTest.java | 3 +- 37 files changed, 349 insertions(+), 402 deletions(-) create mode 100644 src/main/java/org/jabref/collab/DatabaseChangeMonitor.java delete mode 100644 src/main/java/org/jabref/collab/FileUpdateMonitor.java rename src/main/java/org/jabref/{collab => gui/util}/FileUpdateListener.java (63%) create mode 100644 src/main/java/org/jabref/gui/util/FileUpdateMonitor.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 21fd2a531b9..8a3586a7c0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Updated French translation - We improved the handling of abstracts in the "Astrophysics Data System" fetcher. [#2471](https://github.com/JabRef/jabref/issues/2471) - We added support for pasting entries in different formats [#3143](https://github.com/JabRef/jabref/issues/3143) +- In the annotation tab, PDF files are now monitored for new or changed annotation. A manual reload is no longer necessary. [#3292](https://github.com/JabRef/jabref/issues/3292) - We increased the relative size of the "abstract" field in the entry editor. [Feature request in the forum](http://discourse.jabref.org/t/entry-preview-in-version-4/827) - Crossreferenced entries are now used when a BibTex key is generated for an entry with empty fields. [#2811](https://github.com/JabRef/jabref/issues/2811) - We now set the WM_CLASS of the UI to org-jabref-JabRefMain to allow certain Un*x window managers to properly identify its windows diff --git a/src/main/java/org/jabref/Globals.java b/src/main/java/org/jabref/Globals.java index 27fa65c92a6..fb854526e0a 100644 --- a/src/main/java/org/jabref/Globals.java +++ b/src/main/java/org/jabref/Globals.java @@ -4,11 +4,11 @@ import java.util.Optional; import java.util.UUID; -import org.jabref.collab.FileUpdateMonitor; import org.jabref.gui.GlobalFocusListener; import org.jabref.gui.StateManager; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.util.DefaultTaskExecutor; +import org.jabref.gui.util.FileUpdateMonitor; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.journals.JournalAbbreviationLoader; diff --git a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java b/src/main/java/org/jabref/collab/ChangeDisplayDialog.java index 84e21a156a2..0706dbdde9d 100644 --- a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java +++ b/src/main/java/org/jabref/collab/ChangeDisplayDialog.java @@ -103,7 +103,7 @@ public ChangeDisplayDialog(JFrame owner, final BasePanel panel, if (anyDisabled) { panel.markBaseChanged(); } - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); dispose(); okPressed = true; }); diff --git a/src/main/java/org/jabref/collab/ChangeScanner.java b/src/main/java/org/jabref/collab/ChangeScanner.java index c413b583d67..855ce8afe9c 100644 --- a/src/main/java/org/jabref/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/collab/ChangeScanner.java @@ -49,6 +49,7 @@ public class ChangeScanner implements Runnable { private static final double MATCH_THRESHOLD = 0.4; private final File file; + private final Path tempFile; private final BibDatabase databaseInMemory; private final MetaData metadataInMemory; @@ -67,12 +68,13 @@ public class ChangeScanner implements Runnable { // NamedCompound edit = new NamedCompound("Merged external changes") - public ChangeScanner(JabRefFrame frame, BasePanel bp, File file) { + public ChangeScanner(JabRefFrame frame, BasePanel bp, File file, Path tempFile) { this.panel = bp; this.frame = frame; this.databaseInMemory = bp.getDatabase(); this.metadataInMemory = bp.getBibDatabaseContext().getMetaData(); this.file = file; + this.tempFile = tempFile; } @Override @@ -80,7 +82,6 @@ public void run() { try { // Parse the temporary file. - Path tempFile = Globals.getFileUpdateMonitor().getTempFile(panel.fileMonitorHandle()); ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences(); ParserResult result = OpenDatabase.loadDatabase(tempFile.toFile(), importFormatPreferences); databaseInTemp = result.getDatabase(); @@ -153,7 +154,7 @@ private void storeTempDatabase() { Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new); SaveSession ss = databaseWriter.saveDatabase(new BibDatabaseContext(databaseInTemp, metadataInTemp, defaults), prefs); - ss.commit(Globals.getFileUpdateMonitor().getTempFile(panel.fileMonitorHandle())); + ss.commit(tempFile); } catch (SaveException ex) { LOGGER.warn("Problem updating tmp file after accepting external changes", ex); } diff --git a/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java b/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java new file mode 100644 index 00000000000..bce1af7d10d --- /dev/null +++ b/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java @@ -0,0 +1,161 @@ +package org.jabref.collab; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import javax.swing.SwingUtilities; + +import org.jabref.JabRefExecutorService; +import org.jabref.gui.BasePanel; +import org.jabref.gui.SidePaneManager; +import org.jabref.gui.util.FileUpdateListener; +import org.jabref.gui.util.FileUpdateMonitor; +import org.jabref.logic.util.io.FileBasedLock; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class DatabaseChangeMonitor implements FileUpdateListener { + private static final Log LOGGER = LogFactory.getLog(DatabaseChangeMonitor.class); + + private final BibDatabaseContext database; + private final FileUpdateMonitor fileMonitor; + private final BasePanel panel; + private boolean updatedExternally; + private Path tmpFile; + private long timeStamp; + private long fileSize; + + public DatabaseChangeMonitor(BibDatabaseContext database, FileUpdateMonitor fileMonitor, BasePanel panel) { + this.database = database; + this.fileMonitor = fileMonitor; + this.panel = panel; + + this.database.getDatabasePath().ifPresent(path -> { + try { + fileMonitor.addListenerForFile(path, this); + timeStamp = Files.getLastModifiedTime(path).toMillis(); + fileSize = Files.size(path); + tmpFile = Files.createTempFile("jabref", ".bib"); + tmpFile.toFile().deleteOnExit(); + copyToTemp(path); + } catch (IOException e) { + LOGGER.error("Error while trying to monitor " + path, e); + } + }); + } + + @Override + public void fileUpdated() { + if (panel.isSaving()) { + // We are just saving the file, so this message is most likely due to bad timing. + // If not, we'll handle it on the next polling. + return; + } + + updatedExternally = true; + + final ChangeScanner scanner = new ChangeScanner(panel.frame(), panel, database.getDatabaseFile().orElse(null), tmpFile); + + // Test: running scan automatically in background + if (database.getDatabasePath().isPresent() && !FileBasedLock.waitForFileLock(database.getDatabasePath().get())) { + // The file is locked even after the maximum wait. Do nothing. + LOGGER.error("File updated externally, but change scan failed because the file is locked."); + + // Wait a bit and then try again + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Nothing to do + } + fileUpdated(); + return; + } + + JabRefExecutorService.INSTANCE.executeInterruptableTaskAndWait(scanner); + + // Adding the sidepane component is Swing work, so we must do this in the Swing + // thread: + Runnable t = () -> { + + // Check if there is already a notification about external + // changes: + SidePaneManager sidePaneManager = panel.getSidePaneManager(); + boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.class); + if (hasAlready) { + sidePaneManager.hideComponent(FileUpdatePanel.class); + sidePaneManager.unregisterComponent(FileUpdatePanel.class); + } + FileUpdatePanel pan = new FileUpdatePanel(panel, sidePaneManager, + database.getDatabaseFile().orElse(null), scanner); + sidePaneManager.register(pan); + sidePaneManager.show(FileUpdatePanel.class); + }; + + if (scanner.changesFound()) { + SwingUtilities.invokeLater(t); + } else { + updatedExternally = false; + } + } + + /** + * Forces a check on the file, and returns the result. Check if time stamp or the file size has changed. + * + * @return boolean true if the file has changed. + */ + private boolean hasBeenModified() { + Optional file = database.getDatabasePath(); + if (file.isPresent()) { + try { + long modified = Files.getLastModifiedTime(file.get()).toMillis(); + if (modified == 0L) { + // File deleted + return false; + } + long fileSizeNow = Files.size(file.get()); + return (timeStamp != modified) || (fileSize != fileSizeNow); + } catch (IOException ex) { + return false; + } + } + return false; + } + + public void unregister() { + database.getDatabasePath().ifPresent(file -> fileMonitor.removeListener(file, this)); + } + + public boolean hasBeenModifiedExternally() { + return updatedExternally || hasBeenModified(); + } + + public void markExternalChangesAsResolved() { + updatedExternally = false; + } + + public void markAsSaved() { + database.getDatabasePath().ifPresent(file -> { + try { + timeStamp = Files.getLastModifiedTime(file).toMillis(); + fileSize = Files.size(file); + + copyToTemp(file); + } catch (IOException ex) { + LOGGER.error("Error while getting file information", ex); + } + }); + } + + private void copyToTemp(Path file) { + FileUtil.copyFile(file, tmpFile, true); + } + + public Path getTempFile() { + return tmpFile; + } +} diff --git a/src/main/java/org/jabref/collab/FileUpdateMonitor.java b/src/main/java/org/jabref/collab/FileUpdateMonitor.java deleted file mode 100644 index 9d009f19848..00000000000 --- a/src/main/java/org/jabref/collab/FileUpdateMonitor.java +++ /dev/null @@ -1,221 +0,0 @@ -package org.jabref.collab; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; - -import org.jabref.logic.util.io.FileUtil; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This thread monitors a set of files, each associated with a FileUpdateListener, for changes - * in the file's last modification time stamp. The - */ -public class FileUpdateMonitor implements Runnable { - private static final Log LOGGER = LogFactory.getLog(FileUpdateMonitor.class); - - private static final int WAIT = 4000; - private final Map entries = new HashMap<>(); - private int numberOfUpdateListener; - - private static synchronized Path getTempFile() { - Path temporaryFile = null; - try { - temporaryFile = Files.createTempFile("jabref", null); - temporaryFile.toFile().deleteOnExit(); - } catch (IOException ex) { - LOGGER.warn("Could not create temporary file.", ex); - } - return temporaryFile; - } - - @Override - public void run() { - // The running variable is used to make the thread stop when needed. - while (true) { - for (Entry e : entries.values()) { - try { - if (e.hasBeenUpdated()) { - e.notifyListener(); - } - } catch (IOException ex) { - e.notifyFileRemoved(); - } - } - - // Sleep for a while before starting a new polling round. - try { - Thread.sleep(WAIT); - } catch (InterruptedException ex) { - LOGGER.debug("FileUpdateMonitor has been interrupted. Terminating...", ex); - return; - } - } - } - - /** - * Add a new file to monitor. Returns a handle for accessing the entry. - * @param ul FileUpdateListener The listener to notify when the file changes. - * @param file File The file to monitor. - * @throws IOException if the file does not exist. - */ - public String addUpdateListener(FileUpdateListener ul, File file) throws IOException { - if (!file.exists()) { - throw new IOException("File not found"); - } - numberOfUpdateListener++; - String key = String.valueOf(numberOfUpdateListener); - entries.put(key, new Entry(ul, file.toPath())); - return key; - } - - /** - * Forces a check on the file, and returns the result. Does not - * force a report to all listeners before the next routine check. - */ - public boolean hasBeenModified(String handle) { - Entry entry = entries.get(handle); - if (entry == null) { - return false; - } - try { - return entry.hasBeenUpdated(); - } catch (IOException ex) { - // Thrown if file has been removed. We return false. - return false; - } - } - - /** - * Change the stored timestamp for the given file. If the timestamp equals - * the file's timestamp on disk, after this call the file will appear to - * have been modified. Used if a file has been modified, and the change - * scan fails, in order to ensure successive checks. - * @param handle the handle to the correct file. - */ - public void perturbTimestamp(String handle) { - Entry entry = entries.get(handle); - if (entry != null) { - entry.decreaseTimeStamp(); - } - } - - /** - * Removes a listener from the monitor. - * @param handle String The handle for the listener to remove. - */ - public void removeUpdateListener(String handle) { - entries.remove(handle); - } - - public void updateTimeStamp(String key) { - Entry entry = entries.get(key); - if (entry != null) { - try { - entry.updateTimeStamp(); - } catch (IOException e) { - LOGGER.error("Couldn't update timestamp", e); - } - } - } - - /** - * Method for getting the temporary file used for this database. The tempfile - * is used for comparison with the changed on-disk version. - * @param key String The handle for this monitor. - * @throws IllegalArgumentException If the handle doesn't correspond to an entry. - * @return Path The temporary file. - */ - public Path getTempFile(String key) throws IllegalArgumentException { - Entry entry = entries.get(key); - if (entry == null) { - throw new IllegalArgumentException("Entry not found"); - } - return entry.getTmpFile(); - } - - /** - * A class containing the File, the FileUpdateListener and the current time stamp for one file. - */ - static class Entry { - - private final FileUpdateListener listener; - private final Path file; - private final Path tmpFile; - private long timeStamp; - private long fileSize; - - - public Entry(FileUpdateListener ul, Path f) throws IOException { - listener = ul; - file = f; - timeStamp = Files.getLastModifiedTime(file).toMillis(); - fileSize = Files.size(file); - tmpFile = FileUpdateMonitor.getTempFile(); - if (tmpFile != null) { - tmpFile.toFile().deleteOnExit(); - copy(); - } - } - - /** - * Check if time stamp or the file size has changed. - * @throws IOException if the file does no longer exist. - * @return boolean true if the file has changed. - */ - public boolean hasBeenUpdated() throws IOException { - long modified = Files.getLastModifiedTime(file).toMillis(); - if (modified == 0L) { - throw new IOException("File deleted"); - } - long fileSizeNow = Files.size(file); - return (timeStamp != modified) || (fileSize != fileSizeNow); - } - - public void updateTimeStamp() throws IOException { - timeStamp = Files.getLastModifiedTime(file).toMillis(); - if (timeStamp == 0L) { - notifyFileRemoved(); - } - fileSize = Files.size(file); - - copy(); - } - - public boolean copy() { - - return FileUtil.copyFile(file, tmpFile, true); - - } - - /** - * Call the listener method to signal that the file has changed. - */ - public void notifyListener() throws IOException { - // Update time stamp. - timeStamp = Files.getLastModifiedTime(file).toMillis(); - fileSize = Files.size(file); - listener.fileUpdated(); - } - - /** - * Call the listener method to signal that the file has been removed. - */ - public void notifyFileRemoved() { - listener.fileRemoved(); - } - - public Path getTmpFile() { - return tmpFile; - } - - public void decreaseTimeStamp() { - timeStamp--; - } - } -} diff --git a/src/main/java/org/jabref/collab/FileUpdatePanel.java b/src/main/java/org/jabref/collab/FileUpdatePanel.java index facbb7408d2..cd502a03edc 100644 --- a/src/main/java/org/jabref/collab/FileUpdatePanel.java +++ b/src/main/java/org/jabref/collab/FileUpdatePanel.java @@ -104,7 +104,7 @@ public void actionPerformed(ActionEvent e) { public void scanResultsResolved(boolean resolved) { if (resolved) { manager.hideComponent(this); - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); } } } diff --git a/src/main/java/org/jabref/gui/AbstractView.java b/src/main/java/org/jabref/gui/AbstractView.java index 33b8a1cc4ca..055d5a69d0e 100644 --- a/src/main/java/org/jabref/gui/AbstractView.java +++ b/src/main/java/org/jabref/gui/AbstractView.java @@ -32,8 +32,7 @@ public Parent getView() { // Notify controller about the stage, where it is displayed view.sceneProperty().addListener((observable, oldValue, newValue) -> { - - if (newValue.getWindow() instanceof Stage) { + if (newValue != null && newValue.getWindow() instanceof Stage) { Stage stage = (Stage) newValue.getWindow(); if (stage != null) { getController().ifPresent(controller -> controller.setStage(stage)); diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 692db1be4d1..1fcec7a9728 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -43,8 +43,7 @@ import org.jabref.Globals; import org.jabref.JabRefExecutorService; -import org.jabref.collab.ChangeScanner; -import org.jabref.collab.FileUpdateListener; +import org.jabref.collab.DatabaseChangeMonitor; import org.jabref.collab.FileUpdatePanel; import org.jabref.gui.actions.Actions; import org.jabref.gui.actions.BaseAction; @@ -116,7 +115,6 @@ import org.jabref.logic.search.SearchQuery; import org.jabref.logic.util.FileExtensions; import org.jabref.logic.util.UpdateField; -import org.jabref.logic.util.io.FileBasedLock; import org.jabref.logic.util.io.FileFinder; import org.jabref.logic.util.io.FileFinders; import org.jabref.logic.util.io.FileUtil; @@ -148,7 +146,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener { +public class BasePanel extends JPanel implements ClipboardOwner { private static final Log LOGGER = LogFactory.getLog(BasePanel.class); @@ -178,9 +176,8 @@ public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListe private EntryEditor currentEditor; private MainTableSelectionListener selectionListener; private JSplitPane splitPane; - private String fileMonitorHandle; private boolean saving; - private boolean updatedExternally; + // AutoCompleter used in the search bar private PersonNameSuggestionProvider searchAutoCompleter; private boolean baseChanged; @@ -201,6 +198,8 @@ public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListe // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); + private DatabaseChangeMonitor changeMonitor; + public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { Objects.requireNonNull(frame); Objects.requireNonNull(bibDatabaseContext); @@ -229,11 +228,7 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { Optional file = bibDatabaseContext.getDatabaseFile(); if (file.isPresent()) { // Register so we get notifications about outside changes to the file. - try { - fileMonitorHandle = Globals.getFileUpdateMonitor().addUpdateListener(this, file.get()); - } catch (IOException ex) { - LOGGER.warn("Could not register FileUpdateMonitor", ex); - } + changeMonitor = new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), this); } else { if (bibDatabaseContext.getDatabase().hasEntries()) { // if the database is not empty and no file is assigned, @@ -1794,71 +1789,12 @@ private void setEntryEditorEnabled(boolean enabled) { } } - public String fileMonitorHandle() { - return fileMonitorHandle; - } - - @Override - public void fileUpdated() { - if (saving) { - // We are just saving the file, so this message is most likely due to bad timing. - // If not, we'll handle it on the next polling. - return; - } - - updatedExternally = true; - - final ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this, - getBibDatabaseContext().getDatabaseFile().orElse(null)); - - // Test: running scan automatically in background - if ((getBibDatabaseContext().getDatabaseFile().isPresent()) - && !FileBasedLock.waitForFileLock(getBibDatabaseContext().getDatabaseFile().get().toPath())) { - // The file is locked even after the maximum wait. Do nothing. - LOGGER.error("File updated externally, but change scan failed because the file is locked."); - // Perturb the stored timestamp so successive checks are made: - Globals.getFileUpdateMonitor().perturbTimestamp(getFileMonitorHandle()); - return; - } - - JabRefExecutorService.INSTANCE.executeInterruptableTaskAndWait(scanner); - - // Adding the sidepane component is Swing work, so we must do this in the Swing - // thread: - Runnable t = () -> { - - // Check if there is already a notification about external - // changes: - boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.class); - if (hasAlready) { - sidePaneManager.hideComponent(FileUpdatePanel.class); - sidePaneManager.unregisterComponent(FileUpdatePanel.class); - } - FileUpdatePanel pan = new FileUpdatePanel(BasePanel.this, sidePaneManager, - getBibDatabaseContext().getDatabaseFile().orElse(null), scanner); - sidePaneManager.register(pan); - sidePaneManager.show(FileUpdatePanel.class); - }; - - if (scanner.changesFound()) { - SwingUtilities.invokeLater(t); - } else { - setUpdatedExternally(false); - } - } - - @Override - public void fileRemoved() { - LOGGER.info("File '" + getBibDatabaseContext().getDatabaseFile().get().getPath() + "' has been deleted."); - } - /** * Perform necessary cleanup when this BasePanel is closed. */ public void cleanUp() { - if (fileMonitorHandle != null) { - Globals.getFileUpdateMonitor().removeUpdateListener(fileMonitorHandle); - } + changeMonitor.unregister(); + // Check if there is a FileUpdatePanel for this BasePanel being shown. If so, // remove it: if (sidePaneManager.hasComponent(FileUpdatePanel.class)) { @@ -1884,19 +1820,11 @@ public BibDatabaseContext getBibDatabaseContext() { } public boolean isUpdatedExternally() { - return updatedExternally; + return changeMonitor.hasBeenModifiedExternally(); } - public void setUpdatedExternally(boolean b) { - updatedExternally = b; - } - - public String getFileMonitorHandle() { - return fileMonitorHandle; - } - - public void setFileMonitorHandle(String fileMonitorHandle) { - this.fileMonitorHandle = fileMonitorHandle; + public void markExternalChangesAsResolved() { + changeMonitor.markExternalChangesAsResolved(); } public SidePaneManager getSidePaneManager() { @@ -2043,6 +1971,19 @@ public FileAnnotationCache getAnnotationCache() { return annotationCache; } + public void resetChangeMonitor() { + changeMonitor.unregister(); + changeMonitor = new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), this); + } + + public void updateTimeStamp() { + changeMonitor.markAsSaved(); + } + + public Path getTempFile() { + return changeMonitor.getTempFile(); + } + private static class SearchAndOpenFile { private final BibEntry entry; diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index 93845ce5dca..7e430afd0d2 100644 --- a/src/main/java/org/jabref/gui/DefaultInjector.java +++ b/src/main/java/org/jabref/gui/DefaultInjector.java @@ -4,6 +4,7 @@ import org.jabref.Globals; import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.gui.util.FileUpdateMonitor; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.preferences.PreferencesService; @@ -35,6 +36,8 @@ private static Object createDependency(Class clazz) { return Globals.journalAbbreviationLoader; } else if (clazz == StateManager.class) { return Globals.stateManager; + } else if (clazz == FileUpdateMonitor.class) { + return Globals.getFileUpdateMonitor(); } else { try { return clazz.newInstance(); diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml index 7f6abc4d52f..d0891792fc4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml @@ -29,14 +29,6 @@

diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java index d29f6677239..6eb90070e11 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java @@ -1,5 +1,7 @@ package org.jabref.gui.entryeditor.fileannotationtab; +import java.nio.file.Path; + import javax.inject.Inject; import javafx.beans.binding.Bindings; @@ -17,6 +19,7 @@ import javafx.scene.text.Text; import org.jabref.gui.AbstractController; +import org.jabref.gui.util.FileUpdateMonitor; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.FileAnnotationCache; @@ -27,7 +30,7 @@ public class FileAnnotationTabController extends AbstractController { @FXML - public ComboBox files; + public ComboBox files; @FXML public ListView annotationList; @FXML @@ -45,10 +48,12 @@ public class FileAnnotationTabController extends AbstractController annotations = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty files = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty files = new SimpleListProperty<>(FXCollections.observableArrayList()); private final ObjectProperty currentAnnotation = new SimpleObjectProperty<>(); private final FileAnnotationCache cache; private final BibEntry entry; - private Map> fileAnnotations; + private Map> fileAnnotations; + private Path currentFile; + private FileUpdateMonitor fileMonitor; + private FileUpdateListener fileListener = this::reloadAnnotations; - public FileAnnotationTabViewModel(FileAnnotationCache cache, BibEntry entry) { + public FileAnnotationTabViewModel(FileAnnotationCache cache, BibEntry entry, FileUpdateMonitor fileMonitor) { this.cache = cache; this.entry = entry; + this.fileMonitor = fileMonitor; initialize(); } @@ -50,7 +63,7 @@ public ListProperty annotationsProperty() { return annotations; } - public ListProperty filesProperty() { + public ListProperty filesProperty() { return files; } @@ -58,21 +71,37 @@ public void notifyNewSelectedAnnotation(FileAnnotationViewModel newAnnotation) { currentAnnotation.set(newAnnotation); } - public void notifyNewSelectedFile(String newFile) { + public void notifyNewSelectedFile(Path newFile) { + fileMonitor.removeListener(currentFile, fileListener); + currentFile = newFile; + Comparator byPage = Comparator.comparingInt(FileAnnotation::getPage); - List newAnnotations = fileAnnotations.getOrDefault(newFile, new ArrayList<>()) + List newAnnotations = fileAnnotations.getOrDefault(currentFile, new ArrayList<>()) .stream() .filter(annotation -> (null != annotation.getContent())) .sorted(byPage) .map(FileAnnotationViewModel::new) .collect(Collectors.toList()); annotations.setAll(newAnnotations); + + try { + fileMonitor.addListenerForFile(currentFile, fileListener); + } catch (IOException e) { + LOGGER.error("Problem while watching file for changes " + currentFile, e); + } } - public void reloadAnnotations() { - cache.remove(entry); - initialize(); + private void reloadAnnotations() { + // Make sure to always run this in the JavaFX thread as the file monitor (and its notifications) live in a different thread + DefaultTaskExecutor.runInJavaFXThread(() -> { + // Remove annotations for the current entry and reinitialize annotation/cache + cache.remove(entry); + initialize(); + + // Pretend that we just switched to the current file in order to refresh the display + notifyNewSelectedFile(currentFile); + }); } /** diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index f0bb87f64f2..f54bb076a99 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -1,7 +1,6 @@ package org.jabref.gui.exporter; import java.io.File; -import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; @@ -145,7 +144,7 @@ public void run() { panel.getBibDatabaseContext().getMetaData().getEncoding() .orElse(Globals.prefs.getDefaultEncoding())); - Globals.getFileUpdateMonitor().updateTimeStamp(panel.getFileMonitorHandle()); + panel.updateTimeStamp(); } else { success = false; fileLockedError = true; @@ -161,7 +160,7 @@ public void run() { // since last save: panel.setNonUndoableChange(false); panel.setBaseChanged(false); - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); } } catch (SaveException ex) { if (ex == SaveException.FILE_LOCKED) { @@ -346,14 +345,7 @@ public void saveAs(File file) throws Exception { LOGGER.info("Old file not found, just creating a new file"); } context.setDatabaseFile(file); - - // Register so we get notifications about outside changes to the file. - try { - panel.setFileMonitorHandle(Globals.getFileUpdateMonitor().addUpdateListener(panel, - context.getDatabaseFile().orElse(null))); - } catch (IOException ex) { - LOGGER.error("Problem registering file change notifications", ex); - } + panel.resetChangeMonitor(); if (readyForAutosave(context)) { AutosaveManager autosaver = AutosaveManager.start(context); @@ -408,8 +400,7 @@ public boolean isCanceled() { */ private boolean checkExternalModification() { // Check for external modifications: - if (panel.isUpdatedExternally() - || Globals.getFileUpdateMonitor().hasBeenModified(panel.getFileMonitorHandle())) { + if (panel.isUpdatedExternally()) { String[] opts = new String[] {Localization.lang("Review changes"), Localization.lang("Save"), Localization.lang("Cancel")}; int answer = JOptionPane.showOptionDialog(panel.frame(), @@ -432,12 +423,12 @@ private boolean checkExternalModification() { } ChangeScanner scanner = new ChangeScanner(panel.frame(), panel, - panel.getBibDatabaseContext().getDatabaseFile().get()); + panel.getBibDatabaseContext().getDatabaseFile().get(), panel.getTempFile()); JabRefExecutorService.INSTANCE.executeInterruptableTaskAndWait(scanner); if (scanner.changesFound()) { scanner.displayResult(resolved -> { if (resolved) { - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); SwingUtilities .invokeLater(() -> panel.getSidePaneManager().hide(FileUpdatePanel.class)); } else { @@ -456,7 +447,7 @@ private boolean checkExternalModification() { Localization.lang("Protected library"), JOptionPane.ERROR_MESSAGE); canceled = true; } else { - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); panel.getSidePaneManager().hide(FileUpdatePanel.class); } } diff --git a/src/main/java/org/jabref/collab/FileUpdateListener.java b/src/main/java/org/jabref/gui/util/FileUpdateListener.java similarity index 63% rename from src/main/java/org/jabref/collab/FileUpdateListener.java rename to src/main/java/org/jabref/gui/util/FileUpdateListener.java index 6e7b4024ce2..9941fc28816 100644 --- a/src/main/java/org/jabref/collab/FileUpdateListener.java +++ b/src/main/java/org/jabref/gui/util/FileUpdateListener.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.util; public interface FileUpdateListener { @@ -6,10 +6,4 @@ public interface FileUpdateListener { * The file has been updated. A new call will not result until the file has been modified again. */ void fileUpdated(); - - /** - * The file does no longer exist. - */ - void fileRemoved(); - } diff --git a/src/main/java/org/jabref/gui/util/FileUpdateMonitor.java b/src/main/java/org/jabref/gui/util/FileUpdateMonitor.java new file mode 100644 index 00000000000..57f59a0e563 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/FileUpdateMonitor.java @@ -0,0 +1,88 @@ +package org.jabref.gui.util; + +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This class monitors a set of files for changes. Upon detecting a change it notifies the registered {@link + * FileUpdateListener}s. + * + * Implementation based on https://stackoverflow.com/questions/16251273/can-i-watch-for-single-file-change-with-watchservice-not-the-whole-directory + */ +public class FileUpdateMonitor implements Runnable { + private static final Log LOGGER = LogFactory.getLog(FileUpdateMonitor.class); + + private final Multimap listeners = ArrayListMultimap.create(20, 4); + private WatchService watcher; + + @Override + public void run() { + try (WatchService watcher = FileSystems.getDefault().newWatchService()) { + this.watcher = watcher; + while (true) { + WatchKey key; + try { + key = watcher.take(); + } catch (InterruptedException e) { + return; + } + + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + + if (kind == StandardWatchEventKinds.OVERFLOW) { + Thread.yield(); + continue; + } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { + // We only handle "ENTRY_MODIFY" here, so the context is always a Path + @SuppressWarnings("unchecked") + WatchEvent ev = (WatchEvent) event; + Path path = ((Path) key.watchable()).resolve(ev.context()); + notifyAboutChange(path); + } + key.reset(); + } + Thread.yield(); + } + } catch (Throwable e) { + LOGGER.debug("FileUpdateMonitor has been interrupted. Terminating...", e); + } + } + + private void notifyAboutChange(Path path) { + listeners.get(path).forEach(FileUpdateListener::fileUpdated); + } + + /** + * Add a new file to monitor. + * + * @param file The file to monitor. + * @throws IOException if the file does not exist. + */ + public void addListenerForFile(Path file, FileUpdateListener listener) throws IOException { + // We can't watch files directly, so monitor their parent directory for updates + Path directory = file.getParent(); + directory.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); + + listeners.put(file, listener); + } + + /** + * Removes a listener from the monitor. + * + * @param path The path to remove. + */ + public void removeListener(Path path, FileUpdateListener listener) { + listeners.remove(path, listener); + } +} diff --git a/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java b/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java index fffcf282a84..b2e8c1557bc 100644 --- a/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java +++ b/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java @@ -1,5 +1,6 @@ package org.jabref.logic.pdf; +import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,14 +44,14 @@ private List getFilteredFileList() { * @param databaseContext The context is needed for the importer. * @return Map from each PDF to a list of file annotations */ - public Map> importAnnotationsFromFiles(BibDatabaseContext databaseContext) { - Map> annotations = new HashMap<>(); + public Map> importAnnotationsFromFiles(BibDatabaseContext databaseContext) { + Map> annotations = new HashMap<>(); AnnotationImporter importer = new PdfAnnotationImporter(); //import annotationsOfFiles if the selected files are valid which is checked in getFilteredFileList() for (LinkedFile linkedFile : this.getFilteredFileList()) { linkedFile.findIn(databaseContext, JabRefPreferences.getInstance().getFileDirectoryPreferences()) - .ifPresent(file -> annotations.put(file.getFileName().toString(), importer.importAnnotations(file))); + .ifPresent(file -> annotations.put(file, importer.importAnnotations(file))); } return annotations; } diff --git a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java index fece6fb1a10..bbfdbb031ba 100644 --- a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java +++ b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java @@ -1,5 +1,6 @@ package org.jabref.logic.pdf; +import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -20,7 +21,7 @@ public class FileAnnotationCache { private final static int CACHE_SIZE = 10; //the inner list holds the annotations per file, the outer collection maps this to a BibEntry. - private LoadingCache>> annotationCache; + private LoadingCache>> annotationCache; /** * Creates an empty fil annotation cache. Required to allow the annotation cache to be injected into views without @@ -31,9 +32,9 @@ public FileAnnotationCache() { } public FileAnnotationCache(BibDatabaseContext context) { - annotationCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader>>() { + annotationCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader>>() { @Override - public Map> load(BibEntry entry) throws Exception { + public Map> load(BibEntry entry) throws Exception { return new EntryAnnotationImporter(entry).importAnnotationsFromFiles(context); } }); @@ -45,7 +46,7 @@ public Map> load(BibEntry entry) throws Exception { * @param entry entry for which to get the annotations * @return Map containing a list of annotations in a list for each file */ - public Map> getFromCache(BibEntry entry) { + public Map> getFromCache(BibEntry entry) { LOGGER.debug(String.format("Loading Bibentry '%s' from cache.", entry.getCiteKeyOptional().orElse(entry.getId()))); return annotationCache.getUnchecked(entry); } diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index ab05c46caa4..65cd98df5d2 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF-filen_findes_ikke File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Import_fra_ren_tekst Please_enter_a_name_for_the_group.=Skriv_et_navn_til_gruppen. diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 180d5d64c11..83dea0b6cfb 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_existiert_nicht File_has_no_attached_annotations=Datei_hat_keine_angefügten_Annotationen -Reload_annotations=Annotationen_erneut_laden - Plain_text_import=Klartext_importieren Please_enter_a_name_for_the_group.=Bitte_geben_Sie_einen_Namen_für_die_Gruppe_ein. diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 9f0c30a79c7..05f875c196a 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -899,8 +899,6 @@ PDF_does_not_exist= File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import= Please_enter_a_name_for_the_group.= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 11b7b454ee1..87abfe1ce71 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_does_not_exist File_has_no_attached_annotations=File_has_no_attached_annotations -Reload_annotations=Reload_annotations - Plain_text_import=Plain_text_import Please_enter_a_name_for_the_group.=Please_enter_a_name_for_the_group. diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index f5fdde0b827..542112d1651 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=No_existe_el_PDF File_has_no_attached_annotations=El_fichero_no_tiene_anotaciones_adjuntas -Reload_annotations=Recargar_anotaciones - Plain_text_import=Importar_texto_plano Please_enter_a_name_for_the_group.=Introduzca_un_nombre_para_el_grupo. diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 081f667b150..24dd3b29d54 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -899,8 +899,6 @@ PDF_does_not_exist= File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import= Please_enter_a_name_for_the_group.= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 233366ca516..1769d78b613 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=Le_PDF_n'existe_pas File_has_no_attached_annotations=Le_fichier_n'a_pas_d'annotations_liées -Reload_annotations=Recharger_les_annotations - Plain_text_import=Importation_de_texte_brut Please_enter_a_name_for_the_group.=SVP,_entrez_un_nom_pour_le_groupe. diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 312eb8e2a24..9e3c200840a 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_tidak_ada File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Impor_teks_normal Please_enter_a_name_for_the_group.=Tuliskan_nama_untuk_grup. diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 9ee5455765c..737394f837b 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=Il_file_PDF_non_esiste File_has_no_attached_annotations=Il_file_non_ha_annotazioni_allegate -Reload_annotations=Ricarica_le_annotazioni - Plain_text_import=Importazione_da_solo_testo Please_enter_a_name_for_the_group.=Immettere_un_nome_per_il_gruppo diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index ae7215e4608..1064e1fcf0a 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDFが存在しません File_has_no_attached_annotations=ファイルには註釈が付けられていません -Reload_annotations=註釈を再度読み込み - Plain_text_import=平文読み込み Please_enter_a_name_for_the_group.=このグループ用の名称を入力してください. diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index e5dc78ab445..b16c2f31a2e 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -899,8 +899,6 @@ PDF_does_not_exist= File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Onopgemaakte_tekst_importeren Please_enter_a_name_for_the_group.=Geef_a.u.b._een_naam_voor_de_groep. diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 4b2cc134b20..1b8348287ae 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF-filen_finnes_ikke File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Import_fra_ren_tekst Please_enter_a_name_for_the_group.=Skriv_inn_et_navn_for_gruppen. diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 39bf9ad03a4..f5d54193550 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=O_PDF_não_existe File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Importação_de_texto_puro Please_enter_a_name_for_the_group.=Por_favor,_digite_um_nome_para_o_grupo. diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 08b1b952819..a26fdb69084 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_не_существует File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Импорт_неформатированного_текста Please_enter_a_name_for_the_group.=Введите_имя_для_группы. diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index ee2d06ca94e..5c457860186 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_finns_inte File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import= Please_enter_a_name_for_the_group.=Ange_ett_namn_för_gruppen. diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 494bd4c7ae3..985f5187150 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_mevcut_değil File_has_no_attached_annotations=Dosyanın_eklenmiş_açıklama_notu_yok -Reload_annotations=Açıklama_notlarını_yeniden_yükle - Plain_text_import=Düz_metin_içe_aktarma Please_enter_a_name_for_the_group.=Lütfen_grup_için_bir_isim_giriniz. diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 6caad8a18a7..79b95120b63 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_không_tồn_tại File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Nhập_văn_bản_trơn Please_enter_a_name_for_the_group.=Vui_lòng_nhập_tên_cho_nhóm. diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index db3f0a04710..8d56d18cd9e 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_不存在 File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=纯文本导入 Please_enter_a_name_for_the_group.=请为该分组输入一个名字 diff --git a/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java b/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java index 70c0015277d..99689673e30 100644 --- a/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java +++ b/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java @@ -1,6 +1,7 @@ package org.jabref.logic.pdf; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; @@ -36,7 +37,7 @@ public void readEntryExampleThesis() { EntryAnnotationImporter entryAnnotationImporter = new EntryAnnotationImporter(entry); //when - Map> annotations = entryAnnotationImporter.importAnnotationsFromFiles(databaseContext); + Map> annotations = entryAnnotationImporter.importAnnotationsFromFiles(databaseContext); //then int fileCounter = 0; From f71c5a8c775e2239a8e8fdeee12ef0a43d7a6340 Mon Sep 17 00:00:00 2001 From: Patrick Scheibe Date: Sat, 21 Oct 2017 22:30:43 +0200 Subject: [PATCH 7/7] Initializing EntryEditor Tabs on focus (#3331) * Setting up the tab when it is initialized. All tabs that are not visible (end from FieldsEditor) will cost no memory or CPU * Added entry to changelog --- CHANGELOG.md | 2 ++ .../java/org/jabref/gui/entryeditor/FieldsEditorTab.java | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3586a7c0d..efa1aeccdf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We now set the WM_CLASS of the UI to org-jabref-JabRefMain to allow certain Un*x window managers to properly identify its windows - We changed the default paths for the OpenOffice/LibreOffice binaries to the default path for LibreOffice - We no longer create a new entry editor when selecting a new entry to increase performance. [#3187](https://github.com/JabRef/jabref/pull/3187) +- We increased performance and decreased the memory footprint of the entry editor drastically. [#3331](https://github.com/JabRef/jabref/pull/3331) + ### Fixed - We fixed the translation of \textendash in the entry preview [#3307](https://github.com/JabRef/jabref/issues/3307) diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 394e539a41a..1b9b5581a3c 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -39,19 +39,21 @@ */ class FieldsEditorTab extends EntryEditorTab { - private final Region panel; private final List fields; private final EntryEditor parent; private final Map editors = new LinkedHashMap<>(); private final JabRefFrame frame; private final BasePanel basePanel; private final BibEntry entry; + private final boolean isCompressed; + private Region panel; private FieldEditorFX activeField; + public FieldsEditorTab(JabRefFrame frame, BasePanel basePanel, List fields, EntryEditor parent, boolean addKeyField, boolean compressed, BibEntry entry) { this.entry = Objects.requireNonNull(entry); this.fields = new ArrayList<>(Objects.requireNonNull(fields)); - + this.isCompressed = compressed; // Add the edit field for Bibtex-key. if (addKeyField) { this.fields.add(BibEntry.KEY_FIELD); @@ -61,8 +63,6 @@ public FieldsEditorTab(JabRefFrame frame, BasePanel basePanel, List fiel this.frame = frame; this.basePanel = basePanel; - panel = setupPanel(frame, basePanel, compressed); - // The following line makes sure focus cycles inside tab instead of being lost to other parts of the frame: //panel.setFocusCycleRoot(true); } @@ -305,6 +305,7 @@ public void requestFocus() { @Override protected void initialize() { + panel = setupPanel(frame, basePanel, isCompressed); setContent(panel); } }