diff --git a/CHANGELOG.md b/CHANGELOG.md index 72c639ea0e2..b1159856b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We added a few properties to a group: - Icon (with customizable color) that is shown in the groups panel (implements a [feature request in the forum](http://discourse.jabref.org/t/assign-colors-to-groups/321)). - Description text that is shown on mouse hover (implements old feature requests [489](https://sourceforge.net/p/jabref/feature-requests/489/) and [818](https://sourceforge.net/p/jabref/feature-requests/818/) -- We introduced "automatic groups" that automatically create subgroups based on a certain criteria (e.g. a subgroup for every author or keyword). Implements [91](https://sourceforge.net/p/jabref/feature-requests/91/), [398](https://sourceforge.net/p/jabref/feature-requests/398/) and [#1173](https://github.com/JabRef/jabref/issues/1173). +- We introduced "automatic groups" that automatically create subgroups based on a certain criteria (e.g. a subgroup for every author or keyword) and supports hierarchies. Implements [91](https://sourceforge.net/p/jabref/feature-requests/91/), [398](https://sourceforge.net/p/jabref/feature-requests/398/) and [#1173](https://github.com/JabRef/jabref/issues/1173) and [#628](https://github.com/JabRef/jabref/issues/628). - Expansion status of groups are saved across sessions. [#1428](https://github.com/JabRef/jabref/issues/1428) - We removed the ordinals-to-superscript formatter from the recommendations for biblatex save actions [#2596](https://github.com/JabRef/jabref/issues/2596) - The `Move linked files to default file directory`-Cleanup operation respects the `File directory pattern` setting @@ -64,6 +64,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Entries with a single corporate author are now correclty exported to the corresponding `corporate` author field in MS-Office XML. [#1497](https://github.com/JabRef/jabref/issues/1497) - Improved author handling in MS-Office Import/Export - The `day` part of the biblatex `date` field is now exported to the corresponding `day` field in MS-Office XML. [#2691](https://github.com/JabRef/jabref/issues/2691) +- Single underscores are not converted during the LaTeX to unicode conversion, which does not follow the rules of LaTeX, but is what users require. [#2664](https://github.com/JabRef/jabref/issues/2664) ### Fixed - We fixed an issue of duplicate keys after using a fetcher, e.g., DOI or ISBN [#2867](https://github.com/JabRef/jabref/issues/2687) diff --git a/build.gradle b/build.gradle index 9ff197bff8f..6aa55bf4e36 100644 --- a/build.gradle +++ b/build.gradle @@ -139,6 +139,9 @@ dependencies { compile 'com.github.tomtung:latex2unicode_2.12:0.2' + compile group: 'com.microsoft.azure', name: 'applicationinsights-core', version: '1.0.+' + compile group: 'com.microsoft.azure', name: 'applicationinsights-logging-log4j2', version: '1.0.+' + testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.7.21' testCompile 'com.github.tomakehurst:wiremock:2.5.1' @@ -163,7 +166,8 @@ processResources { expand(version: project.version, "year": String.valueOf(Calendar.getInstance().get(Calendar.YEAR)), "authors": new File('AUTHORS').readLines().findAll { !it.startsWith("#") }.join(", "), - "developers": new File('DEVELOPERS').readLines().findAll { !it.startsWith("#") }.join(", ")) + "developers": new File('DEVELOPERS').readLines().findAll { !it.startsWith("#") }.join(", "), + "azureInstrumentationKey": System.getenv('AzureInstrumentationKey')) filteringCharset = 'UTF-8' } filteringCharset = 'UTF-8' diff --git a/src/main/java/org/jabref/Globals.java b/src/main/java/org/jabref/Globals.java index b41d21027fb..f3e40b17609 100644 --- a/src/main/java/org/jabref/Globals.java +++ b/src/main/java/org/jabref/Globals.java @@ -1,5 +1,8 @@ package org.jabref; +import java.awt.Toolkit; +import java.util.UUID; + import org.jabref.collab.FileUpdateMonitor; import org.jabref.gui.GlobalFocusListener; import org.jabref.gui.StateManager; @@ -13,6 +16,11 @@ import org.jabref.logic.util.BuildInfo; import org.jabref.preferences.JabRefPreferences; +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.TelemetryConfiguration; +import com.microsoft.applicationinsights.telemetry.SessionState; +import org.apache.commons.lang3.SystemUtils; + public class Globals { // JabRef version info @@ -44,6 +52,7 @@ public class Globals { // Background tasks private static GlobalFocusListener focusListener; private static FileUpdateMonitor fileUpdateMonitor; + private static TelemetryClient telemetryClient; private Globals() { } @@ -63,6 +72,30 @@ public static void startBackgroundTasks() { Globals.fileUpdateMonitor = new FileUpdateMonitor(); JabRefExecutorService.INSTANCE.executeInterruptableTask(Globals.fileUpdateMonitor, "FileUpdateMonitor"); + + startTelemetryClient(); + } + + private static void stopTelemetryClient() { + telemetryClient.trackSessionState(SessionState.End); + telemetryClient.flush(); + } + + private static void startTelemetryClient() { + TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.getActive(); + telemetryConfiguration.setInstrumentationKey(Globals.BUILD_INFO.getAzureInstrumentationKey()); + telemetryConfiguration.setTrackingIsDisabled(!Globals.prefs.shouldCollectTelemetry()); + telemetryClient = new TelemetryClient(telemetryConfiguration); + telemetryClient.getContext().getProperties().put("JabRef version", Globals.BUILD_INFO.getVersion().toString()); + telemetryClient.getContext().getProperties().put("Java version", SystemUtils.JAVA_RUNTIME_VERSION); + telemetryClient.getContext().getUser().setId(Globals.prefs.getOrCreateUserId()); + telemetryClient.getContext().getSession().setId(UUID.randomUUID().toString()); + telemetryClient.getContext().getDevice().setOperatingSystem(SystemUtils.OS_NAME); + telemetryClient.getContext().getDevice().setOperatingSystemVersion(SystemUtils.OS_VERSION); + telemetryClient.getContext().getDevice().setScreenResolution( + Toolkit.getDefaultToolkit().getScreenSize().toString()); + + telemetryClient.trackSessionState(SessionState.Start); } public static GlobalFocusListener getFocusListener() { @@ -77,4 +110,12 @@ public static void shutdownThreadPools() { taskExecutor.shutdown(); JabRefExecutorService.INSTANCE.shutdownEverything(); } + + public static void stopBackgroundTasks() { + stopTelemetryClient(); + } + + public static TelemetryClient getTelemetryClient() { + return telemetryClient; + } } diff --git a/src/main/java/org/jabref/JabRefMain.java b/src/main/java/org/jabref/JabRefMain.java index cf7b1440b24..931c640a508 100644 --- a/src/main/java/org/jabref/JabRefMain.java +++ b/src/main/java/org/jabref/JabRefMain.java @@ -64,8 +64,8 @@ private static void start(String[] args) { Authenticator.setDefault(new ProxyAuthenticator()); } - Globals.startBackgroundTasks(); Globals.prefs = preferences; + Globals.startBackgroundTasks(); Localization.setLanguage(preferences.get(JabRefPreferences.LANGUAGE)); Globals.prefs.setLanguageDependentDefaultValues(); diff --git a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java b/src/main/java/org/jabref/collab/ChangeDisplayDialog.java index 99ea002802d..84e21a156a2 100644 --- a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java +++ b/src/main/java/org/jabref/collab/ChangeDisplayDialog.java @@ -9,7 +9,6 @@ import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; @@ -21,11 +20,12 @@ import javax.swing.tree.DefaultMutableTreeNode; import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.undo.NamedCompound; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabase; -class ChangeDisplayDialog extends JDialog implements TreeSelectionListener { +class ChangeDisplayDialog extends JabRefDialog implements TreeSelectionListener { private final JTree tree; private final JPanel infoPanel = new JPanel(); @@ -38,7 +38,7 @@ class ChangeDisplayDialog extends JDialog implements TreeSelectionListener { public ChangeDisplayDialog(JFrame owner, final BasePanel panel, BibDatabase secondary, final DefaultMutableTreeNode root) { - super(owner, Localization.lang("External changes"), true); + super(owner, Localization.lang("External changes"), true, ChangeDisplayDialog.class); BibDatabase localSecondary; // Just to be sure, put in an empty secondary base if none is given: diff --git a/src/main/java/org/jabref/gui/DialogService.java b/src/main/java/org/jabref/gui/DialogService.java index a970b17357d..dab1847298d 100644 --- a/src/main/java/org/jabref/gui/DialogService.java +++ b/src/main/java/org/jabref/gui/DialogService.java @@ -64,7 +64,7 @@ default void showErrorDialogAndWait(Exception exception) { /** * This will create and display a new confirmation dialog. * It will include a blue question icon on the left and - * a OK and Cancel Button. To create a confirmation dialog with custom + * a OK and Cancel button. To create a confirmation dialog with custom * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} * * @return true if the use clicked "OK" otherwise false @@ -74,13 +74,23 @@ default void showErrorDialogAndWait(Exception exception) { /** * Create and display a new confirmation dialog. * It will include a blue question icon on the left and - * a OK (with given label) and Cancel Button. To create a confirmation dialog with custom + * a OK (with given label) and Cancel button. To create a confirmation dialog with custom * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} * * @return true if the use clicked "OK" otherwise false */ boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel); + /** + * Create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a OK (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + * + * @return true if the use clicked "OK" otherwise false + */ + boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, String cancelButtonLabel); + /** * This will create and display a new dialog of the specified * {@link Alert.AlertType} but with user defined buttons as optional diff --git a/src/main/java/org/jabref/gui/DuplicateResolverDialog.java b/src/main/java/org/jabref/gui/DuplicateResolverDialog.java index 1815b841cd9..346987ff39d 100644 --- a/src/main/java/org/jabref/gui/DuplicateResolverDialog.java +++ b/src/main/java/org/jabref/gui/DuplicateResolverDialog.java @@ -6,7 +6,6 @@ import javax.swing.Box; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JPanel; import org.jabref.gui.help.HelpAction; @@ -18,7 +17,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.preferences.JabRefPreferences; -public class DuplicateResolverDialog extends JDialog { +public class DuplicateResolverDialog extends JabRefDialog { public enum DuplicateResolverType { DUPLICATE_SEARCH, @@ -46,14 +45,14 @@ public enum DuplicateResolverResult { private MergeEntries me; public DuplicateResolverDialog(JabRefFrame frame, BibEntry one, BibEntry two, DuplicateResolverType type) { - super(frame, Localization.lang("Possible duplicate entries"), true); + super(frame, Localization.lang("Possible duplicate entries"), true, DuplicateResolverDialog.class); this.frame = frame; init(one, two, type); } public DuplicateResolverDialog(ImportInspectionDialog dialog, BibEntry one, BibEntry two, DuplicateResolverType type) { - super(dialog, Localization.lang("Possible duplicate entries"), true); + super(dialog, Localization.lang("Possible duplicate entries"), true, DuplicateResolverDialog.class); this.frame = dialog.getFrame(); init(one, two, type); } diff --git a/src/main/java/org/jabref/gui/EntryTypeDialog.java b/src/main/java/org/jabref/gui/EntryTypeDialog.java index 159e88b11f8..d33320340d2 100644 --- a/src/main/java/org/jabref/gui/EntryTypeDialog.java +++ b/src/main/java/org/jabref/gui/EntryTypeDialog.java @@ -18,7 +18,6 @@ import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -51,7 +50,7 @@ * Dialog that prompts the user to choose a type for an entry. * Returns null if canceled. */ -public class EntryTypeDialog extends JDialog implements ActionListener { +public class EntryTypeDialog extends JabRefDialog implements ActionListener { private static final Log LOGGER = LogFactory.getLog(EntryTypeDialog.class); private static final int COLUMN = 3; @@ -65,7 +64,7 @@ public class EntryTypeDialog extends JDialog implements ActionListener { public EntryTypeDialog(JabRefFrame frame) { // modal dialog - super(frame, true); + super(frame, true, EntryTypeDialog.class); this.frame = frame; diff --git a/src/main/java/org/jabref/gui/FXDialogService.java b/src/main/java/org/jabref/gui/FXDialogService.java index c5ebe2a79d7..9fb9d104090 100644 --- a/src/main/java/org/jabref/gui/FXDialogService.java +++ b/src/main/java/org/jabref/gui/FXDialogService.java @@ -81,6 +81,15 @@ public boolean showConfirmationDialogAndWait(String title, String content, Strin return alert.showAndWait().filter(buttonType -> buttonType == okButtonType).isPresent(); } + @Override + public boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, String cancelButtonLabel) { + FXDialog alert = createDialog(AlertType.CONFIRMATION, title, content); + ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); + ButtonType cancelButtonType = new ButtonType(cancelButtonLabel, ButtonBar.ButtonData.NO); + alert.getButtonTypes().setAll(okButtonType, cancelButtonType); + return alert.showAndWait().filter(buttonType -> buttonType == okButtonType).isPresent(); + } + @Override public Optional showCustomButtonDialogAndWait(AlertType type, String title, String content, ButtonType... buttonTypes) { diff --git a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java index 2f146314fe7..8d479077dda 100644 --- a/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java +++ b/src/main/java/org/jabref/gui/FindUnlinkedFilesDialog.java @@ -42,7 +42,6 @@ import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; @@ -88,7 +87,7 @@ /** * GUI Dialog for the feature "Find unlinked files". */ -public class FindUnlinkedFilesDialog extends JDialog { +public class FindUnlinkedFilesDialog extends JabRefDialog { private static final Log LOGGER = LogFactory.getLog(FindUnlinkedFilesDialog.class); /** @@ -159,18 +158,8 @@ public class FindUnlinkedFilesDialog extends JDialog { private boolean checkBoxWhyIsThereNoGetSelectedStupidSwing; - /** - * For Unit-testing only. Don't remove!
- * Used via reflection in {@link org.jabref.logic.importer.DatabaseFileLookupTest} to construct this - * class. - */ - @SuppressWarnings("unused") - private FindUnlinkedFilesDialog() { - //intended - } - public FindUnlinkedFilesDialog(Frame owner, JabRefFrame frame, BasePanel panel) { - super(owner, Localization.lang("Find unlinked files"), true); + super(owner, Localization.lang("Find unlinked files"), true, FindUnlinkedFilesDialog.class); this.frame = frame; restoreSizeOfDialog(); diff --git a/src/main/java/org/jabref/gui/GenFieldsCustomizer.java b/src/main/java/org/jabref/gui/GenFieldsCustomizer.java index 5dade80b8d3..d7b93d7b349 100644 --- a/src/main/java/org/jabref/gui/GenFieldsCustomizer.java +++ b/src/main/java/org/jabref/gui/GenFieldsCustomizer.java @@ -14,7 +14,6 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -33,7 +32,7 @@ import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.layout.Sizes; -public class GenFieldsCustomizer extends JDialog { +public class GenFieldsCustomizer extends JabRefDialog { private final JPanel buttons = new JPanel(); private final JButton ok = new JButton(); @@ -50,7 +49,7 @@ public class GenFieldsCustomizer extends JDialog { private final JButton revert = new JButton(); public GenFieldsCustomizer(JabRefFrame frame) { - super(frame, Localization.lang("Set general fields"), false); + super(frame, Localization.lang("Set general fields"), false, GenFieldsCustomizer.class); helpBut = new HelpAction(HelpFile.GENERAL_FIELDS).getHelpButton(); jbInit(); setSize(new Dimension(650, 300)); diff --git a/src/main/java/org/jabref/gui/JabRefDialog.java b/src/main/java/org/jabref/gui/JabRefDialog.java new file mode 100644 index 00000000000..c691694dac7 --- /dev/null +++ b/src/main/java/org/jabref/gui/JabRefDialog.java @@ -0,0 +1,59 @@ +package org.jabref.gui; + +import java.awt.Frame; +import java.awt.Window; + +import javax.swing.JDialog; + +import org.jabref.Globals; + +public class JabRefDialog extends JDialog { + + public JabRefDialog(Frame owner, boolean modal, Class clazz) { + super(owner, modal); + + trackDialogOpening(clazz); + } + + public JabRefDialog(Class clazz) { + super(); + + trackDialogOpening(clazz); + } + + public JabRefDialog(Frame owner, Class clazz) { + super(owner); + + trackDialogOpening(clazz); + } + + public JabRefDialog(Window owner, String title, Class clazz) { + super(owner, title); + + trackDialogOpening(clazz); + } + + private void trackDialogOpening(Class clazz) { + Globals.getTelemetryClient().trackPageView(clazz.getName()); + } + + public JabRefDialog(Frame owner, String title, Class clazz) { + this(owner, title, true, clazz); + } + + public JabRefDialog(Frame owner, String title, boolean modal, Class clazz) { + super(owner, title, modal); + + trackDialogOpening(clazz); + } + + public JabRefDialog(java.awt.Dialog owner, String title, Class clazz) { + this(owner, title, true, clazz); + } + + public JabRefDialog(java.awt.Dialog owner, String title, boolean modal, Class clazz) { + super(owner, title, modal); + + trackDialogOpening(clazz); + } +} diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 95494891112..60c8d3618ca 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -22,10 +22,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.swing.AbstractAction; import javax.swing.Action; @@ -105,6 +110,7 @@ import org.jabref.gui.search.GlobalSearchBar; import org.jabref.gui.specialfields.SpecialFieldDropDown; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.WindowLocation; import org.jabref.gui.worker.MarkEntriesAction; import org.jabref.logic.autosaveandbackup.AutosaveManager; @@ -645,6 +651,31 @@ public void windowClosing(WindowEvent e) { } } + initShowTrackingNotification(); + + } + + private void initShowTrackingNotification() { + if (!Globals.prefs.shouldAskToCollectTelemetry()) { + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.scheduleWithFixedDelay(() -> DefaultTaskExecutor.runInJavaFXThread(this::showTrackingNotification), 1, 1, TimeUnit.MINUTES); + } + } + + private Void showTrackingNotification() { + if (!Globals.prefs.shouldCollectTelemetry()) { + DialogService dialogService = new FXDialogService(); + boolean shouldCollect = dialogService.showConfirmationDialogAndWait( + Localization.lang("Telemetry: Help make JabRef better"), + Localization.lang("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."), + Localization.lang("Share anonymous statistics"), + Localization.lang("Don't share")); + Globals.prefs.setShouldCollectTelemetry(shouldCollect); + } + + Globals.prefs.askedToCollectTelemetry(); + + return null; } public void refreshTitleAndTabs() { @@ -733,6 +764,7 @@ public JabRefPreferences prefs() { * @param filenames the filenames of all currently opened files - used for storing them if prefs openLastEdited is set to true */ private void tearDownJabRef(List filenames) { + Globals.stopBackgroundTasks(); Globals.shutdownThreadPools(); dispose(); @@ -1536,6 +1568,18 @@ public void addTab(BasePanel basePanel, boolean raisePanel) { } BackupManager.start(context); + + // Track opening + trackOpenNewDatabase(basePanel); + } + + private void trackOpenNewDatabase(BasePanel basePanel) { + + Map properties = new HashMap<>(); + Map measurements = new HashMap<>(); + measurements.put("NumberOfEntries", (double)basePanel.getDatabaseContext().getDatabase().getEntryCount()); + + Globals.getTelemetryClient().trackEvent("OpenNewDatabase", properties, measurements); } public BasePanel addTab(BibDatabaseContext databaseContext, boolean raisePanel) { diff --git a/src/main/java/org/jabref/gui/MergeDialog.java b/src/main/java/org/jabref/gui/MergeDialog.java index 232bfbfb71b..805e19010a2 100644 --- a/src/main/java/org/jabref/gui/MergeDialog.java +++ b/src/main/java/org/jabref/gui/MergeDialog.java @@ -13,7 +13,6 @@ import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JPanel; import org.jabref.Globals; @@ -27,7 +26,7 @@ * @author Morten O. Alver */ -public class MergeDialog extends JDialog { +public class MergeDialog extends JabRefDialog { private final JPanel panel1 = new JPanel(); private final BorderLayout borderLayout1 = new BorderLayout(); @@ -45,7 +44,7 @@ public class MergeDialog extends JDialog { private boolean okPressed; public MergeDialog(JabRefFrame frame, String title, boolean modal) { - super(frame, title, modal); + super(frame, title, modal, MergeDialog.class); jbInit(); pack(); } diff --git a/src/main/java/org/jabref/gui/PreambleEditor.java b/src/main/java/org/jabref/gui/PreambleEditor.java index d68bcce06ad..b6afcd9112b 100644 --- a/src/main/java/org/jabref/gui/PreambleEditor.java +++ b/src/main/java/org/jabref/gui/PreambleEditor.java @@ -14,7 +14,6 @@ import javax.swing.AbstractAction; import javax.swing.Action; -import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.text.JTextComponent; @@ -30,7 +29,7 @@ import org.jabref.model.database.BibDatabase; import org.jabref.preferences.JabRefPreferences; -class PreambleEditor extends JDialog { +class PreambleEditor extends JabRefDialog { // A reference to the entry this object works on. private final BibDatabase database; private final BasePanel panel; @@ -44,7 +43,7 @@ class PreambleEditor extends JDialog { private final CloseAction closeAction = new CloseAction(); public PreambleEditor(JabRefFrame baseFrame, BasePanel panel, BibDatabase database) { - super(baseFrame); + super(baseFrame, PreambleEditor.class); this.panel = panel; this.database = database; diff --git a/src/main/java/org/jabref/gui/ReplaceStringDialog.java b/src/main/java/org/jabref/gui/ReplaceStringDialog.java index ff3068e1cec..8198f93d983 100644 --- a/src/main/java/org/jabref/gui/ReplaceStringDialog.java +++ b/src/main/java/org/jabref/gui/ReplaceStringDialog.java @@ -16,7 +16,6 @@ import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; @@ -32,7 +31,7 @@ /** * Dialog for replacing strings. */ -class ReplaceStringDialog extends JDialog { +class ReplaceStringDialog extends JabRefDialog { private final JTextField fieldsField = new JTextField("", 30); private final JTextField fromField = new JTextField("", 30); @@ -48,7 +47,7 @@ class ReplaceStringDialog extends JDialog { public ReplaceStringDialog(JabRefFrame parent) { - super(parent, Localization.lang("Replace string"), true); + super(parent, Localization.lang("Replace string"), true, ReplaceStringDialog.class); ButtonGroup bg = new ButtonGroup(); bg.add(allFi); diff --git a/src/main/java/org/jabref/gui/StringDialog.java b/src/main/java/org/jabref/gui/StringDialog.java index 207cb38fa63..d79dbb67383 100644 --- a/src/main/java/org/jabref/gui/StringDialog.java +++ b/src/main/java/org/jabref/gui/StringDialog.java @@ -19,7 +19,6 @@ import javax.swing.DefaultCellEditor; import javax.swing.InputMap; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -49,7 +48,7 @@ import org.jabref.model.entry.BibtexString; import org.jabref.preferences.JabRefPreferences; -class StringDialog extends JDialog { +class StringDialog extends JabRefDialog { private static final String STRINGS_TITLE = Localization.lang("Strings for library"); // A reference to the entry this object works on. @@ -66,7 +65,7 @@ class StringDialog extends JDialog { public StringDialog(JabRefFrame frame, BasePanel panel, BibDatabase base) { - super(frame); + super(frame, StringDialog.class); this.panel = panel; this.base = base; diff --git a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java index d391f3cdc18..e18a111bf27 100644 --- a/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java +++ b/src/main/java/org/jabref/gui/auximport/FromAuxDialog.java @@ -12,7 +12,6 @@ import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; @@ -24,6 +23,7 @@ import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.FileDialog; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.auxparser.AuxParser; @@ -39,7 +39,7 @@ /** * A wizard dialog for generating a new sub database from existing TeX AUX file */ -public class FromAuxDialog extends JDialog { +public class FromAuxDialog extends JabRefDialog { private final JPanel statusPanel = new JPanel(); private final JPanel buttons = new JPanel(); @@ -64,7 +64,7 @@ public class FromAuxDialog extends JDialog { public FromAuxDialog(JabRefFrame frame, String title, boolean modal, JTabbedPane viewedDBs) { - super(frame, title, modal); + super(frame, title, modal, FromAuxDialog.class); parentTabbedPane = viewedDBs; parentFrame = frame; diff --git a/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternDialog.java b/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternDialog.java index 739e9392c88..503f2368107 100644 --- a/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternDialog.java +++ b/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternDialog.java @@ -15,6 +15,7 @@ import org.jabref.Globals; import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.keyboard.KeyBinder; import org.jabref.logic.l10n.Localization; @@ -23,7 +24,7 @@ import com.jgoodies.forms.builder.ButtonBarBuilder; -public class BibtexKeyPatternDialog extends JDialog { +public class BibtexKeyPatternDialog extends JabRefDialog { private MetaData metaData; private BasePanel panel; @@ -31,7 +32,7 @@ public class BibtexKeyPatternDialog extends JDialog { public BibtexKeyPatternDialog(JabRefFrame parent, BasePanel panel) { - super(parent, Localization.lang("BibTeX key patterns"), true); + super(parent, Localization.lang("BibTeX key patterns"), true, BibtexKeyPatternDialog.class); this.bibtexKeyPatternPanel = new BibtexKeyPatternPanel(panel); setPanel(panel); init(); diff --git a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialog.java b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialog.java index da130ef8b89..a05a4a5d213 100644 --- a/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialog.java +++ b/src/main/java/org/jabref/gui/contentselector/ContentSelectorDialog.java @@ -22,7 +22,6 @@ import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -30,6 +29,7 @@ import javax.swing.JTextField; import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.help.HelpAction; import org.jabref.gui.keyboard.KeyBinder; @@ -44,7 +44,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -public class ContentSelectorDialog extends JDialog { +public class ContentSelectorDialog extends JabRefDialog { private final GridBagLayout gbl = new GridBagLayout(); private final GridBagConstraints con = new GridBagConstraints(); @@ -91,7 +91,7 @@ public class ContentSelectorDialog extends JDialog { * @param fieldName the field this selector is initialized for. May be null. */ public ContentSelectorDialog(Window owner, JabRefFrame frame, BasePanel panel, boolean modal, String fieldName) { - super(owner, Localization.lang("Manage content selectors")); + super(owner, Localization.lang("Manage content selectors"), ContentSelectorDialog.class); this.setModal(modal); this.metaData = panel.getBibDatabaseContext().getMetaData(); this.frame = frame; diff --git a/src/main/java/org/jabref/gui/customentrytypes/EntryCustomizationDialog.java b/src/main/java/org/jabref/gui/customentrytypes/EntryCustomizationDialog.java index f1697767f77..9be44d6b270 100644 --- a/src/main/java/org/jabref/gui/customentrytypes/EntryCustomizationDialog.java +++ b/src/main/java/org/jabref/gui/customentrytypes/EntryCustomizationDialog.java @@ -24,7 +24,6 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -37,6 +36,7 @@ import org.jabref.Globals; import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.l10n.Localization; @@ -50,7 +50,7 @@ import com.jgoodies.forms.builder.ButtonBarBuilder; -public class EntryCustomizationDialog extends JDialog implements ListSelectionListener { +public class EntryCustomizationDialog extends JabRefDialog implements ListSelectionListener { private final JabRefFrame frame; protected GridBagLayout gbl = new GridBagLayout(); @@ -79,7 +79,7 @@ public class EntryCustomizationDialog extends JDialog implements ListSelectionLi * Creates a new instance of EntryCustomizationDialog */ public EntryCustomizationDialog(JabRefFrame frame) { - super(frame, Localization.lang("Customize entry types"), false); + super(frame, Localization.lang("Customize entry types"), false, EntryCustomizationDialog.class); this.frame = frame; initGui(); diff --git a/src/main/java/org/jabref/gui/dbproperties/DatabasePropertiesDialog.java b/src/main/java/org/jabref/gui/dbproperties/DatabasePropertiesDialog.java index 4096cd88d47..96ea63e748c 100644 --- a/src/main/java/org/jabref/gui/dbproperties/DatabasePropertiesDialog.java +++ b/src/main/java/org/jabref/gui/dbproperties/DatabasePropertiesDialog.java @@ -16,7 +16,6 @@ import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JRadioButton; import javax.swing.JTextField; @@ -24,6 +23,7 @@ import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.FileDialog; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.SaveOrderConfigDisplay; import org.jabref.gui.cleanup.FieldFormatterCleanupsPanel; import org.jabref.gui.help.HelpAction; @@ -40,7 +40,7 @@ import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.layout.FormLayout; -public class DatabasePropertiesDialog extends JDialog { +public class DatabasePropertiesDialog extends JabRefDialog { private MetaData metaData; private BasePanel panel; @@ -67,7 +67,7 @@ public class DatabasePropertiesDialog extends JDialog { public DatabasePropertiesDialog(JFrame parent) { - super(parent, Localization.lang("Library properties"), true); + super(parent, Localization.lang("Library properties"), true, DatabasePropertiesDialog.class); encoding = new JComboBox<>(); encoding.setModel(new DefaultComboBoxModel<>(Encodings.ENCODINGS)); ok = new JButton(Localization.lang("OK")); diff --git a/src/main/java/org/jabref/gui/exporter/CustomExportDialog.java b/src/main/java/org/jabref/gui/exporter/CustomExportDialog.java index 6793da3564b..029d0a75081 100644 --- a/src/main/java/org/jabref/gui/exporter/CustomExportDialog.java +++ b/src/main/java/org/jabref/gui/exporter/CustomExportDialog.java @@ -15,13 +15,13 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import org.jabref.Globals; import org.jabref.gui.FileDialog; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.l10n.Localization; @@ -35,7 +35,7 @@ /** * Dialog for creating or modifying custom exports. */ -class CustomExportDialog extends JDialog { +class CustomExportDialog extends JabRefDialog { private static final Log LOGGER = LogFactory.getLog(CustomExportDialog.class); private final JTextField name = new JTextField(60); @@ -54,7 +54,7 @@ public CustomExportDialog(final JabRefFrame parent, final String exporterName, f } public CustomExportDialog(final JabRefFrame parent) { - super(parent, Localization.lang("Edit custom export"), true); + super(parent, Localization.lang("Edit custom export"), true, CustomExportDialog.class); frame = parent; ActionListener okListener = e -> { Path layoutFileDir = Paths.get(layoutFile.getText()).getParent(); diff --git a/src/main/java/org/jabref/gui/exporter/ExportCustomizationDialog.java b/src/main/java/org/jabref/gui/exporter/ExportCustomizationDialog.java index 0a5a3d2dbb6..9effdcc1839 100644 --- a/src/main/java/org/jabref/gui/exporter/ExportCustomizationDialog.java +++ b/src/main/java/org/jabref/gui/exporter/ExportCustomizationDialog.java @@ -14,7 +14,6 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; @@ -23,6 +22,7 @@ import javax.swing.table.TableColumnModel; import org.jabref.Globals; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.help.HelpAction; import org.jabref.gui.keyboard.KeyBinding; @@ -44,7 +44,7 @@ * @version 1.0 */ -public class ExportCustomizationDialog extends JDialog { +public class ExportCustomizationDialog extends JabRefDialog { // Column widths for export customization dialog table: private static final int COL_0_WIDTH = 50; @@ -53,7 +53,7 @@ public class ExportCustomizationDialog extends JDialog { public ExportCustomizationDialog(final JabRefFrame frame) { - super(frame, Localization.lang("Manage custom exports"), false); + super(frame, Localization.lang("Manage custom exports"), false, ExportCustomizationDialog.class); DefaultEventTableModel> tableModel = new DefaultEventTableModel<>( Globals.prefs.customExports.getSortedList(), new ExportTableFormat()); JTable table = new JTable(tableModel); diff --git a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java index 95873a491aa..a8ebc3a83c5 100644 --- a/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java +++ b/src/main/java/org/jabref/gui/externalfiles/SynchronizeFileField.java @@ -21,7 +21,6 @@ import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; @@ -32,6 +31,7 @@ import org.jabref.JabRefExecutorService; import org.jabref.gui.BasePanel; import org.jabref.gui.IconTheme; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.externalfiletype.ExternalFileType; import org.jabref.gui.externalfiletype.ExternalFileTypeEntryEditor; import org.jabref.gui.externalfiletype.ExternalFileTypes; @@ -282,7 +282,7 @@ public void update() { } - static class OptionsDialog extends JDialog { + static class OptionsDialog extends JabRefDialog { private final JButton ok = new JButton(Localization.lang("OK")); @@ -298,7 +298,7 @@ static class OptionsDialog extends JDialog { public OptionsDialog(JFrame parent, BibDatabaseContext databaseContext) { - super(parent, Localization.lang("Synchronize file links"), true); + super(parent, Localization.lang("Synchronize file links"), true, OptionsDialog.class); this.databaseContext = databaseContext; ok.addActionListener(e -> { canceled = false; diff --git a/src/main/java/org/jabref/gui/externalfiles/WriteXMPAction.java b/src/main/java/org/jabref/gui/externalfiles/WriteXMPAction.java index 41c8559fcf7..51b007dc1c2 100644 --- a/src/main/java/org/jabref/gui/externalfiles/WriteXMPAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/WriteXMPAction.java @@ -15,7 +15,6 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -26,6 +25,7 @@ import org.jabref.Globals; import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.filelist.FileListEntry; import org.jabref.gui.filelist.FileListTableModel; import org.jabref.gui.keyboard.KeyBinding; @@ -201,7 +201,7 @@ public void update() { } - class OptionsDialog extends JDialog { + class OptionsDialog extends JabRefDialog { private final JButton okButton = new JButton(Localization.lang("OK")); private final JButton cancelButton = new JButton(Localization.lang("Cancel")); @@ -212,7 +212,7 @@ class OptionsDialog extends JDialog { public OptionsDialog(JFrame parent) { - super(parent, Localization.lang("Writing XMP-metadata for selected entries..."), false); + super(parent, Localization.lang("Writing XMP-metadata for selected entries..."), false, OptionsDialog.class); okButton.setEnabled(false); okButton.addActionListener(e -> dispose()); diff --git a/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypeEditor.java b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypeEditor.java index 0236c781a63..a7ef4cbacd9 100644 --- a/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypeEditor.java +++ b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypeEditor.java @@ -31,6 +31,7 @@ import org.jabref.Globals; import org.jabref.gui.IconTheme; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.actions.MnemonicAwareAction; import org.jabref.gui.keyboard.KeyBinding; @@ -42,7 +43,7 @@ /** * Editor for external file types. */ -public class ExternalFileTypeEditor extends JDialog { +public class ExternalFileTypeEditor extends JabRefDialog { private JFrame frame; private JDialog dialog; @@ -60,13 +61,13 @@ public class ExternalFileTypeEditor extends JDialog { private ExternalFileTypeEditor(JFrame frame) { - super(frame, Localization.lang("Manage external file types"), true); + super(frame, Localization.lang("Manage external file types"), true, ExternalFileTypeEditor.class); this.frame = frame; init(); } private ExternalFileTypeEditor(JDialog dialog) { - super(dialog, Localization.lang("Manage external file types"), true); + super(dialog, Localization.lang("Manage external file types"), true, ExternalFileTypeEditor.class); this.dialog = dialog; init(); } diff --git a/src/main/java/org/jabref/gui/groups/GroupDialog.java b/src/main/java/org/jabref/gui/groups/GroupDialog.java index 026175cdaf7..d23bb45047f 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialog.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialog.java @@ -18,7 +18,6 @@ import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; @@ -33,12 +32,14 @@ import org.jabref.Globals; import org.jabref.JabRefGUI; import org.jabref.gui.Dialog; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.fieldeditors.TextField; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.l10n.Localization; import org.jabref.logic.search.SearchQuery; import org.jabref.model.entry.FieldName; +import org.jabref.model.entry.Keyword; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.groups.AutomaticKeywordGroup; import org.jabref.model.groups.AutomaticPersonsGroup; @@ -59,7 +60,7 @@ * Dialog for creating or modifying groups. Operates directly on the Vector * containing group information. */ -class GroupDialog extends JDialog implements Dialog { +class GroupDialog extends JabRefDialog implements Dialog { private static final int INDEX_EXPLICIT_GROUP = 0; private static final int INDEX_KEYWORD_GROUP = 1; @@ -100,6 +101,7 @@ class GroupDialog extends JDialog implements Dialog { Localization.lang("Generate groups from keywords in a BibTeX field")); private final JTextField autoGroupKeywordsField = new JTextField(60); private final JTextField autoGroupKeywordsDeliminator = new JTextField(60); + private final JTextField autoGroupKeywordsHierarchicalDeliminator = new JTextField(60); private final JRadioButton autoGroupPersonsOption = new JRadioButton( Localization.lang("Generate groups for author last names")); private final JTextField autoGroupPersonsField = new JTextField(60); @@ -130,7 +132,7 @@ public Dimension getPreferredSize() { * created. */ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { - super(jabrefFrame, Localization.lang("Edit group"), true); + super(jabrefFrame, Localization.lang("Edit group"), true, GroupDialog.class); // set default values (overwritten if editedGroup != null) keywordGroupSearchField.setText(jabrefFrame.prefs().get(JabRefPreferences.GROUPS_DEFAULT_FIELD)); @@ -183,7 +185,7 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { bg.add(autoGroupPersonsOption); FormLayout layoutAutoGroup = new FormLayout("left:20dlu, 4dlu, left:pref, 4dlu, fill:60dlu", - "p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p"); + "p, 2dlu, p, 2dlu, p, p, 2dlu, p, 2dlu, p"); FormBuilder builderAutoGroup = FormBuilder.create(); builderAutoGroup.layout(layoutAutoGroup); builderAutoGroup.add(autoGroupKeywordsOption).xyw(1, 1, 5); @@ -191,14 +193,16 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { builderAutoGroup.add(autoGroupKeywordsField).xy(5, 3); builderAutoGroup.add(Localization.lang("Use the following delimiter character(s):")).xy(3, 5); builderAutoGroup.add(autoGroupKeywordsDeliminator).xy(5, 5); - builderAutoGroup.add(autoGroupPersonsOption).xyw(1, 7, 5); - builderAutoGroup.add(Localization.lang("Field to group by") + ":").xy(3, 9); - builderAutoGroup.add(autoGroupPersonsField).xy(5, 9); + builderAutoGroup.add(autoGroupKeywordsHierarchicalDeliminator).xy(5, 6); + builderAutoGroup.add(autoGroupPersonsOption).xyw(1, 8, 5); + builderAutoGroup.add(Localization.lang("Field to group by") + ":").xy(3, 10); + builderAutoGroup.add(autoGroupPersonsField).xy(5, 10); optionsPanel.add(builderAutoGroup.build(), String.valueOf(GroupDialog.INDEX_AUTO_GROUP)); autoGroupKeywordsOption.setSelected(true); autoGroupKeywordsField.setText(Globals.prefs.get(JabRefPreferences.GROUPS_DEFAULT_FIELD)); autoGroupKeywordsDeliminator.setText(Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR)); + autoGroupKeywordsHierarchicalDeliminator.setText(Keyword.DEFAULT_HIERARCHICAL_DELIMITER.toString()); autoGroupPersonsField.setText(FieldName.AUTHOR); // ... for buttons panel @@ -349,9 +353,11 @@ public void actionPerformed(ActionEvent e) { } } else if (autoRadioButton.isSelected()) { if (autoGroupKeywordsOption.isSelected()) { - resultingGroup = new AutomaticKeywordGroup(nameField.getText().trim(), getContext(), + resultingGroup = new AutomaticKeywordGroup( + nameField.getText().trim(), getContext(), autoGroupKeywordsField.getText().trim(), - autoGroupKeywordsDeliminator.getText().charAt(0)); + autoGroupKeywordsDeliminator.getText().charAt(0), + autoGroupKeywordsHierarchicalDeliminator.getText().charAt(0)); } else { resultingGroup = new AutomaticPersonsGroup(nameField.getText().trim(), getContext(), autoGroupPersonsField.getText().trim()); @@ -429,7 +435,8 @@ public void actionPerformed(ActionEvent e) { if (editedGroup.getClass() == AutomaticKeywordGroup.class) { AutomaticKeywordGroup group = (AutomaticKeywordGroup) editedGroup; - autoGroupKeywordsDeliminator.setText(group.getKeywordSeperator().toString()); + autoGroupKeywordsDeliminator.setText(group.getKeywordDelimiter().toString()); + autoGroupKeywordsHierarchicalDeliminator.setText(group.getKeywordHierarchicalDelimiter().toString()); autoGroupKeywordsField.setText(group.getField()); } else if (editedGroup.getClass() == AutomaticPersonsGroup.class) { AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup; diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index 7d649024515..a73496975cb 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -1,14 +1,9 @@ package org.jabref.gui.groups; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -67,16 +62,12 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state if (groupNode.getGroup() instanceof AutomaticGroup) { AutomaticGroup automaticGroup = (AutomaticGroup) groupNode.getGroup(); - // TODO: Update on changes to entry list (however: there is no flatMap and filter as observable TransformationLists) - children = databaseContext.getDatabase() - .getEntries().stream() - .flatMap(stream -> createSubgroups(automaticGroup, stream)) - .filter(distinctByKey(group -> group.getGroupNode().getName())) + children = automaticGroup.createSubgroups(databaseContext.getDatabase().getEntries()).stream() + .map(this::toViewModel) .sorted((group1, group2) -> group1.getDisplayName().compareToIgnoreCase(group2.getDisplayName())) .collect(Collectors.toCollection(FXCollections::observableArrayList)); } else { - children = EasyBind.map(groupNode.getChildren(), - child -> new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, child)); + children = EasyBind.map(groupNode.getChildren(), this::toViewModel); } hasChildren = new SimpleBooleanProperty(); hasChildren.bind(Bindings.isNotEmpty(children)); @@ -97,18 +88,12 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state this(databaseContext, stateManager, taskExecutor, new GroupTreeNode(group)); } - private static Predicate distinctByKey(Function keyExtractor) { - Map seen = new ConcurrentHashMap<>(); - return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; - } - static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, StateManager stateManager, TaskExecutor taskExecutor) { return new GroupNodeViewModel(newDatabase, stateManager, taskExecutor, DefaultGroupsFactory.getAllEntriesGroup()); } - private Stream createSubgroups(AutomaticGroup automaticGroup, BibEntry entry) { - return automaticGroup.createSubgroups(entry).stream() - .map(child -> new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, child)); + private GroupNodeViewModel toViewModel(GroupTreeNode child) { + return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, child); } public List addEntriesToGroup(List entries) { @@ -243,7 +228,7 @@ public String getPath() { } public Optional getChildByPath(String pathToSource) { - return groupNode.getChildByPath(pathToSource).map(child -> new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, child)); + return groupNode.getChildByPath(pathToSource).map(this::toViewModel); } /** diff --git a/src/main/java/org/jabref/gui/help/NewVersionDialog.java b/src/main/java/org/jabref/gui/help/NewVersionDialog.java index c54d082e8c0..9c64e2a64a7 100644 --- a/src/main/java/org/jabref/gui/help/NewVersionDialog.java +++ b/src/main/java/org/jabref/gui/help/NewVersionDialog.java @@ -7,22 +7,22 @@ import java.awt.event.MouseEvent; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.event.MouseInputAdapter; import org.jabref.Globals; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.Version; import org.jabref.preferences.VersionPreferences; -public class NewVersionDialog extends JDialog { +public class NewVersionDialog extends JabRefDialog { public NewVersionDialog(JFrame frame, Version currentVersion, Version latestVersion) { - super(frame); + super(frame, NewVersionDialog.class); setTitle(Localization.lang("New version available")); JLabel lblTitle = new JLabel(Localization.lang("A new version of JabRef has been released.")); diff --git a/src/main/java/org/jabref/gui/importer/FetcherPreviewDialog.java b/src/main/java/org/jabref/gui/importer/FetcherPreviewDialog.java index 01941f1339b..816d66aef01 100644 --- a/src/main/java/org/jabref/gui/importer/FetcherPreviewDialog.java +++ b/src/main/java/org/jabref/gui/importer/FetcherPreviewDialog.java @@ -14,7 +14,6 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -24,6 +23,7 @@ import javax.swing.table.TableModel; import org.jabref.Globals; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.importer.OutputPrinter; @@ -41,7 +41,7 @@ /** * */ -public class FetcherPreviewDialog extends JDialog implements OutputPrinter { +public class FetcherPreviewDialog extends JabRefDialog implements OutputPrinter { private final EventList entries = new BasicEventList<>(); private final JTable glTable; @@ -51,7 +51,7 @@ public class FetcherPreviewDialog extends JDialog implements OutputPrinter { public FetcherPreviewDialog(JabRefFrame frame, int warningLimit, int tableRowHeight) { - super(frame, Localization.lang("Title"), true); + super(frame, Localization.lang("Title"), true, FetcherPreviewDialog.class); this.frame = frame; this.warningLimit = warningLimit; diff --git a/src/main/java/org/jabref/gui/importer/ImportCustomizationDialog.java b/src/main/java/org/jabref/gui/importer/ImportCustomizationDialog.java index e51a9f4247a..3f626bb2819 100644 --- a/src/main/java/org/jabref/gui/importer/ImportCustomizationDialog.java +++ b/src/main/java/org/jabref/gui/importer/ImportCustomizationDialog.java @@ -17,7 +17,6 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -29,6 +28,7 @@ import org.jabref.Globals; import org.jabref.gui.FileDialog; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.help.HelpAction; import org.jabref.gui.keyboard.KeyBinding; @@ -44,7 +44,7 @@ /** * Dialog to manage custom importers. */ -public class ImportCustomizationDialog extends JDialog { +public class ImportCustomizationDialog extends JabRefDialog { private static final Log LOGGER = LogFactory.getLog(ImportCustomizationDialog.class); // Column widths for import customization dialog table: @@ -57,7 +57,7 @@ public class ImportCustomizationDialog extends JDialog { private final JTable customImporterTable; public ImportCustomizationDialog(final JabRefFrame frame) { - super(frame, Localization.lang("Manage custom imports"), false); + super(frame, Localization.lang("Manage custom imports"), false, ImportCustomizationDialog.class); ImportTableModel tableModel = new ImportTableModel(); customImporterTable = new JTable(tableModel); diff --git a/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java b/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java index a6c6d65248a..5906a0adbef 100644 --- a/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java +++ b/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java @@ -56,6 +56,7 @@ import org.jabref.gui.EntryMarker; import org.jabref.gui.GUIGlobals; import org.jabref.gui.IconTheme; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.PreviewPanel; import org.jabref.gui.desktop.JabRefDesktop; @@ -138,7 +139,7 @@ * receiving this call). */ -public class ImportInspectionDialog extends JDialog implements ImportInspector, OutputPrinter { +public class ImportInspectionDialog extends JabRefDialog implements ImportInspector, OutputPrinter { private static final Log LOGGER = LogFactory.getLog(ImportInspectionDialog.class); private static final List INSPECTION_FIELDS = Arrays.asList(FieldName.AUTHOR, FieldName.TITLE, FieldName.YEAR, BibEntry.KEY_FIELD); @@ -189,7 +190,7 @@ public class ImportInspectionDialog extends JDialog implements ImportInspector, * @param panel */ public ImportInspectionDialog(JabRefFrame frame, BasePanel panel, String undoName, boolean newDatabase) { - super(frame); + super(frame, ImportInspectionDialog.class); this.frame = frame; this.panel = panel; this.bibDatabaseContext = (panel == null) ? null : panel.getBibDatabaseContext(); diff --git a/src/main/java/org/jabref/gui/importer/ZipFileChooser.java b/src/main/java/org/jabref/gui/importer/ZipFileChooser.java index 7e7d7db1082..e4290ae8784 100644 --- a/src/main/java/org/jabref/gui/importer/ZipFileChooser.java +++ b/src/main/java/org/jabref/gui/importer/ZipFileChooser.java @@ -17,7 +17,6 @@ import javax.swing.Box; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -27,6 +26,7 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; +import org.jabref.gui.JabRefDialog; import org.jabref.logic.importer.fileformat.CustomImporter; import org.jabref.logic.l10n.Localization; @@ -36,7 +36,7 @@ /** * Dialog to allow users to choose a file contained in a ZIP file. */ -class ZipFileChooser extends JDialog { +class ZipFileChooser extends JabRefDialog { private static final Log LOGGER = LogFactory.getLog(ZipFileChooser.class); @@ -48,7 +48,7 @@ class ZipFileChooser extends JDialog { * @param zipFile ZIP-Fle to choose from, must be readable */ public ZipFileChooser(ImportCustomizationDialog importCustomizationDialog, ZipFile zipFile) { - super(importCustomizationDialog, Localization.lang("Select file from ZIP-archive"), false); + super(importCustomizationDialog, Localization.lang("Select file from ZIP-archive"), false, ZipFileChooser.class); ZipFileChooserTableModel tableModel = new ZipFileChooserTableModel(zipFile, getSelectableZipEntries(zipFile)); diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java index 9e14d7639be..dbb4200862e 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java @@ -3,11 +3,11 @@ import java.util.List; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.JSeparator; import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableInsertEntry; import org.jabref.gui.undo.UndoableRemoveEntry; @@ -27,7 +27,7 @@ * * Dialog for merging two Bibtex entries */ -public class MergeEntriesDialog extends JDialog { +public class MergeEntriesDialog extends JabRefDialog { private final BasePanel panel; private final CellConstraints cc = new CellConstraints(); @@ -36,7 +36,7 @@ public class MergeEntriesDialog extends JDialog { private static final String MARGIN = "5px"; public MergeEntriesDialog(BasePanel panel) { - super(panel.frame(), MERGE_ENTRIES, true); + super(panel.frame(), MERGE_ENTRIES, true, MergeEntriesDialog.class); this.panel = panel; diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeFetchedEntryDialog.java b/src/main/java/org/jabref/gui/mergeentries/MergeFetchedEntryDialog.java index 8c995cedf41..10d801bea46 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeFetchedEntryDialog.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeFetchedEntryDialog.java @@ -8,10 +8,10 @@ import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JSeparator; import org.jabref.gui.BasePanel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableChangeType; import org.jabref.gui.undo.UndoableFieldChange; @@ -30,7 +30,7 @@ /** * Dialog for merging Bibtex entry with fetched data */ -public class MergeFetchedEntryDialog extends JDialog { +public class MergeFetchedEntryDialog extends JabRefDialog { private final BasePanel panel; private final CellConstraints cc = new CellConstraints(); @@ -43,7 +43,7 @@ public class MergeFetchedEntryDialog extends JDialog { public MergeFetchedEntryDialog(BasePanel panel, BibEntry originalEntry, BibEntry fetchedEntry, String type) { - super(panel.frame(), Localization.lang("Merge entry with %0 information", type), true); + super(panel.frame(), Localization.lang("Merge entry with %0 information", type), true, MergeFetchedEntryDialog.class); this.panel = panel; this.originalEntry = originalEntry; diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java index 4704786744d..bf39a9323d5 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java @@ -35,6 +35,7 @@ import org.jabref.Globals; import org.jabref.gui.FileDialog; import org.jabref.gui.IconTheme; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.PreviewPanel; import org.jabref.gui.desktop.JabRefDesktop; @@ -462,14 +463,14 @@ public void listChanged(ListEvent listEvent) { } } - private class AddFileDialog extends JDialog { + private class AddFileDialog extends JabRefDialog { private final JTextField newFile = new JTextField(); private boolean addOKPressed; public AddFileDialog() { - super(diag, Localization.lang("Add style file"), true); + super(diag, Localization.lang("Add style file"), true, AddFileDialog.class); JButton browse = new JButton(Localization.lang("Browse")); FileDialog dialog = new FileDialog(frame).withExtension(FileExtensions.JSTYLE); diff --git a/src/main/java/org/jabref/gui/plaintextimport/TextInputDialog.java b/src/main/java/org/jabref/gui/plaintextimport/TextInputDialog.java index 6f8ecd10e99..c0de866ebd4 100644 --- a/src/main/java/org/jabref/gui/plaintextimport/TextInputDialog.java +++ b/src/main/java/org/jabref/gui/plaintextimport/TextInputDialog.java @@ -34,7 +34,6 @@ import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; @@ -64,6 +63,7 @@ import org.jabref.gui.EntryMarker; import org.jabref.gui.FileDialog; import org.jabref.gui.IconTheme; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.OSXCompatibleToolbar; import org.jabref.gui.keyboard.KeyBinding; @@ -99,7 +99,7 @@ * - create several bibtex entries in dialog * - if the dialog works with an existing entry (right click menu item), the cancel option doesn't work well */ -public class TextInputDialog extends JDialog { +public class TextInputDialog extends JabRefDialog { private static final Log LOGGER = LogFactory.getLog(TextInputDialog.class); @@ -135,7 +135,7 @@ public class TextInputDialog extends JDialog { public TextInputDialog(JabRefFrame frame, BibEntry bibEntry) { - super(frame, true); + super(frame, true, TextInputDialog.class); this.frame = Objects.requireNonNull(frame); diff --git a/src/main/java/org/jabref/gui/preftabs/FontSelectorDialog.java b/src/main/java/org/jabref/gui/preftabs/FontSelectorDialog.java index b8c0047de43..f7b43589922 100644 --- a/src/main/java/org/jabref/gui/preftabs/FontSelectorDialog.java +++ b/src/main/java/org/jabref/gui/preftabs/FontSelectorDialog.java @@ -52,7 +52,6 @@ Portions copyright (C) 1999 Jason Ginchereau import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; @@ -62,6 +61,7 @@ Portions copyright (C) 1999 Jason Ginchereau import javax.swing.border.EmptyBorder; import javax.swing.border.TitledBorder; +import org.jabref.gui.JabRefDialog; import org.jabref.logic.l10n.Localization; /** @@ -111,7 +111,7 @@ Portions copyright (C) 1999 Jason Ginchereau /////////////////////////////////////////////////////////////////////////////// -public class FontSelectorDialog extends JDialog { +public class FontSelectorDialog extends JabRefDialog { private static final String PLAIN = "plain"; private static final String BOLD = "bold"; @@ -141,7 +141,7 @@ public class FontSelectorDialog extends JDialog { public FontSelectorDialog(Component comp, Font font) { - super(JOptionPane.getFrameForComponent(comp), Localization.lang("Font selection"), true); // + super(JOptionPane.getFrameForComponent(comp), Localization.lang("Font selection"), true, FontSelectorDialog.class); // JPanel content = new JPanel(new BorderLayout()); content.setBorder(new EmptyBorder(12, 12, 12, 12)); setContentPane(content); diff --git a/src/main/java/org/jabref/gui/preftabs/GeneralTab.java b/src/main/java/org/jabref/gui/preftabs/GeneralTab.java index c61ff11657b..630a0ef71a2 100644 --- a/src/main/java/org/jabref/gui/preftabs/GeneralTab.java +++ b/src/main/java/org/jabref/gui/preftabs/GeneralTab.java @@ -36,6 +36,7 @@ class GeneralTab extends JPanel implements PrefsTab { private final JCheckBox useOwner; private final JCheckBox overwriteOwner; private final JCheckBox enforceLegalKeys; + private final JCheckBox shouldCollectTelemetry; private final JCheckBox confirmDelete; private final JCheckBox memoryStick; private final JCheckBox inspectionWarnDupli; @@ -93,6 +94,8 @@ public GeneralTab(JabRefPreferences prefs) { timeStampField = new JTextField(); inspectionWarnDupli = new JCheckBox(Localization.lang("Warn about unresolved duplicates when closing inspection window")); + shouldCollectTelemetry = new JCheckBox(Localization.lang("Collect and share telemetry data to help improve JabRef.")); + encodings = new JComboBox<>(); encodings.setModel(new DefaultComboBoxModel<>(Encodings.ENCODINGS)); @@ -139,6 +142,8 @@ public GeneralTab(JabRefPreferences prefs) { builder.nextLine(); builder.append(unmarkAllEntriesBeforeImporting, 13); builder.nextLine(); + builder.append(shouldCollectTelemetry, 13); + builder.nextLine(); JLabel lab; lab = new JLabel(Localization.lang("Language") + ':'); builder.append(lab, 3); @@ -168,6 +173,7 @@ public void setValues() { updateTimeStamp.setSelected(prefs.getBoolean(JabRefPreferences.UPDATE_TIMESTAMP)); updateTimeStamp.setEnabled(useTimeStamp.isSelected()); enforceLegalKeys.setSelected(prefs.getBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY)); + shouldCollectTelemetry.setSelected(prefs.shouldCollectTelemetry()); memoryStick.setSelected(prefs.getBoolean(JabRefPreferences.MEMORY_STICK_MODE)); confirmDelete.setSelected(prefs.getBoolean(JabRefPreferences.CONFIRM_DELETE)); defOwnerField.setText(prefs.get(JabRefPreferences.DEFAULT_OWNER)); @@ -206,6 +212,7 @@ public void storeSettings() { prefs.putBoolean(JabRefPreferences.OVERWRITE_TIME_STAMP, overwriteTimeStamp.isSelected()); prefs.putBoolean(JabRefPreferences.UPDATE_TIMESTAMP, updateTimeStamp.isSelected()); prefs.putBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY, enforceLegalKeys.isSelected()); + prefs.setShouldCollectTelemetry(shouldCollectTelemetry.isSelected()); if (prefs.getBoolean(JabRefPreferences.MEMORY_STICK_MODE) && !memoryStick.isSelected()) { JOptionPane.showMessageDialog(null, Localization.lang("To disable the memory stick mode" + " rename or remove the jabref.xml file in the same folder as JabRef."), diff --git a/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java b/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java index aa60bf92743..aa265edfde4 100644 --- a/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java +++ b/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java @@ -15,7 +15,6 @@ import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; -import javax.swing.JDialog; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -26,6 +25,7 @@ import org.jabref.JabRefException; import org.jabref.gui.FileDialog; import org.jabref.gui.GUIGlobals; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.keyboard.KeyBinder; import org.jabref.gui.maintable.MainTable; @@ -53,7 +53,7 @@ * With this design, it should be very easy to add new tabs later. * */ -public class PreferencesDialog extends JDialog { +public class PreferencesDialog extends JabRefDialog { private static final Log LOGGER = LogFactory.getLog(PreferencesDialog.class); private final JPanel main; @@ -66,7 +66,7 @@ public class PreferencesDialog extends JDialog { private final JButton resetPreferences = new JButton(Localization.lang("Reset preferences")); public PreferencesDialog(JabRefFrame parent) { - super(parent, Localization.lang("JabRef preferences"), false); + super(parent, Localization.lang("JabRef preferences"), false, PreferencesDialog.class); JabRefPreferences prefs = JabRefPreferences.getInstance(); frame = parent; diff --git a/src/main/java/org/jabref/gui/preftabs/PreferencesFilterDialog.java b/src/main/java/org/jabref/gui/preftabs/PreferencesFilterDialog.java index 955bda8b6f2..050a2cb619f 100644 --- a/src/main/java/org/jabref/gui/preftabs/PreferencesFilterDialog.java +++ b/src/main/java/org/jabref/gui/preftabs/PreferencesFilterDialog.java @@ -7,7 +7,6 @@ import java.util.Objects; import javax.swing.JCheckBox; -import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; @@ -15,11 +14,12 @@ import javax.swing.JTable; import javax.swing.table.AbstractTableModel; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.WrapLayout; import org.jabref.logic.l10n.Localization; import org.jabref.preferences.JabRefPreferencesFilter; -class PreferencesFilterDialog extends JDialog { +class PreferencesFilterDialog extends JabRefDialog { private final JabRefPreferencesFilter preferencesFilter; @@ -28,7 +28,7 @@ class PreferencesFilterDialog extends JDialog { private final JLabel count; public PreferencesFilterDialog(JabRefPreferencesFilter preferencesFilter, JFrame frame) { - super(frame, true); // is modal + super(frame, true, PreferencesFilterDialog.class); // is modal this.preferencesFilter = Objects.requireNonNull(preferencesFilter); diff --git a/src/main/java/org/jabref/gui/protectedterms/NewProtectedTermsFileDialog.java b/src/main/java/org/jabref/gui/protectedterms/NewProtectedTermsFileDialog.java index 6009e4ff666..a56be02d7cf 100644 --- a/src/main/java/org/jabref/gui/protectedterms/NewProtectedTermsFileDialog.java +++ b/src/main/java/org/jabref/gui/protectedterms/NewProtectedTermsFileDialog.java @@ -17,6 +17,7 @@ import org.jabref.Globals; import org.jabref.gui.FileDialog; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.l10n.Localization; @@ -27,7 +28,7 @@ import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.layout.FormLayout; -public class NewProtectedTermsFileDialog extends JDialog { +public class NewProtectedTermsFileDialog extends JabRefDialog { private final JTextField newFile = new JTextField(); private final JTextField newDescription = new JTextField(); @@ -37,14 +38,14 @@ public class NewProtectedTermsFileDialog extends JDialog { private JFrame parent; public NewProtectedTermsFileDialog(JDialog parent, ProtectedTermsLoader loader) { - super(parent, Localization.lang("New protected terms file"), true); + super(parent, Localization.lang("New protected terms file"), true, NewProtectedTermsFileDialog.class); this.loader = loader; setupDialog(); setLocationRelativeTo(parent); } public NewProtectedTermsFileDialog(JabRefFrame mainFrame, ProtectedTermsLoader loader) { - super(mainFrame, Localization.lang("New protected terms file"), true); + super(mainFrame, Localization.lang("New protected terms file"), true, NewProtectedTermsFileDialog.class); parent = mainFrame; this.loader = loader; setupDialog(); diff --git a/src/main/java/org/jabref/gui/protectedterms/ProtectedTermsDialog.java b/src/main/java/org/jabref/gui/protectedterms/ProtectedTermsDialog.java index 2f031f6bbd2..634d6912ee9 100644 --- a/src/main/java/org/jabref/gui/protectedterms/ProtectedTermsDialog.java +++ b/src/main/java/org/jabref/gui/protectedterms/ProtectedTermsDialog.java @@ -37,6 +37,7 @@ import org.jabref.Globals; import org.jabref.gui.FileDialog; import org.jabref.gui.IconTheme; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.externalfiletype.ExternalFileType; @@ -421,14 +422,14 @@ public void valueChanged(ListSelectionEvent listEvent) { } } - private class AddFileDialog extends JDialog { + private class AddFileDialog extends JabRefDialog { private final JTextField newFile = new JTextField(); private boolean addOKPressed; public AddFileDialog() { - super(diag, Localization.lang("Add protected terms file"), true); + super(diag, Localization.lang("Add protected terms file"), true, AddFileDialog.class); JButton browse = new JButton(Localization.lang("Browse")); FileDialog dialog = new FileDialog(frame).withExtension(FileExtensions.TERMS); diff --git a/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseDialog.java b/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseDialog.java index d849c2edffc..0b6ea21ed0f 100644 --- a/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseDialog.java +++ b/src/main/java/org/jabref/gui/shared/ConnectToSharedDatabaseDialog.java @@ -24,7 +24,6 @@ import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -36,6 +35,7 @@ import org.jabref.JabRefGUI; import org.jabref.gui.BasePanel; import org.jabref.gui.FileDialog; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.JabRefFrame; import org.jabref.gui.exporter.SaveDatabaseAction; import org.jabref.gui.help.HelpAction; @@ -55,7 +55,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -public class ConnectToSharedDatabaseDialog extends JDialog { +public class ConnectToSharedDatabaseDialog extends JabRefDialog { private static final Log LOGGER = LogFactory.getLog(ConnectToSharedDatabaseDialog.class); @@ -99,7 +99,7 @@ public class ConnectToSharedDatabaseDialog extends JDialog { * @param frame the JabRef Frame */ public ConnectToSharedDatabaseDialog(JabRefFrame frame) { - super(frame, Localization.lang("Connect to shared database")); + super(frame, Localization.lang("Connect to shared database"), ConnectToSharedDatabaseDialog.class); this.frame = frame; initLayout(); updateEnableState(); diff --git a/src/main/java/org/jabref/gui/shared/MigrationHelpDialog.java b/src/main/java/org/jabref/gui/shared/MigrationHelpDialog.java index 083b13f2a12..ce30b9ecb0d 100644 --- a/src/main/java/org/jabref/gui/shared/MigrationHelpDialog.java +++ b/src/main/java/org/jabref/gui/shared/MigrationHelpDialog.java @@ -12,21 +12,20 @@ import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.border.EmptyBorder; +import org.jabref.gui.JabRefDialog; import org.jabref.gui.help.HelpAction; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -public class MigrationHelpDialog extends JDialog { +public class MigrationHelpDialog extends JabRefDialog { public MigrationHelpDialog(ConnectToSharedDatabaseDialog openSharedDatabaseDialog) { - super(openSharedDatabaseDialog, Localization.lang("Migration help information")); - setModal(true); + super(openSharedDatabaseDialog, Localization.lang("Migration help information"), MigrationHelpDialog.class); String migrationMessage = Localization .lang("Entered library has obsolete structure and is no longer supported."); diff --git a/src/main/java/org/jabref/logic/exporter/GroupSerializer.java b/src/main/java/org/jabref/logic/exporter/GroupSerializer.java index b174cfc31e0..a309bfaf76f 100644 --- a/src/main/java/org/jabref/logic/exporter/GroupSerializer.java +++ b/src/main/java/org/jabref/logic/exporter/GroupSerializer.java @@ -152,7 +152,9 @@ private String serializeAutomaticKeywordGroup(AutomaticKeywordGroup group) { appendAutomaticGroupDetails(sb, group); sb.append(StringUtil.quote(group.getField(), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR)); sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR); - sb.append(group.getKeywordSeperator()); + sb.append(group.getKeywordDelimiter()); + sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR); + sb.append(group.getKeywordHierarchicalDelimiter()); sb.append(MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR); appendGroupDetails(sb, group); return sb.toString(); diff --git a/src/main/java/org/jabref/logic/importer/util/GroupsParser.java b/src/main/java/org/jabref/logic/importer/util/GroupsParser.java index bfe04995bac..44b240d42db 100644 --- a/src/main/java/org/jabref/logic/importer/util/GroupsParser.java +++ b/src/main/java/org/jabref/logic/importer/util/GroupsParser.java @@ -127,8 +127,9 @@ private static AbstractGroup automaticKeywordGroupFromString(String string) { String name = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR); GroupHierarchyType context = GroupHierarchyType.getByNumberOrDefault(Integer.parseInt(tok.nextToken())); String field = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR); - Character separator = tok.nextToken().charAt(0); - AutomaticKeywordGroup newGroup = new AutomaticKeywordGroup(name, context, field, separator); + Character delimiter = tok.nextToken().charAt(0); + Character hierarchicalDelimiter = tok.nextToken().charAt(0); + AutomaticKeywordGroup newGroup = new AutomaticKeywordGroup(name, context, field, delimiter, hierarchicalDelimiter); addGroupDetails(tok, newGroup); return newGroup; } diff --git a/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java b/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java index 1946871edef..3be6dd623b1 100644 --- a/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java +++ b/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java @@ -61,36 +61,36 @@ public static MetaData parse(MetaData metaData, Map data, Charac } switch (entry.getKey()) { - case MetaData.GROUPSTREE: - metaData.setGroups(GroupsParser.importGroups(value, keywordSeparator)); - break; - case MetaData.SAVE_ACTIONS: - metaData.setSaveActions(Cleanups.parse(value)); - break; - case MetaData.DATABASE_TYPE: - metaData.setMode(BibDatabaseMode.parse(getSingleItem(value))); - break; - case MetaData.KEYPATTERNDEFAULT: - defaultCiteKeyPattern = Collections.singletonList(getSingleItem(value)); - break; - case MetaData.PROTECTED_FLAG_META: - if (Boolean.parseBoolean(getSingleItem(value))) { - metaData.markAsProtected(); - } else { - metaData.markAsNotProtected(); - } - break; - case MetaData.FILE_DIRECTORY: - metaData.setDefaultFileDirectory(getSingleItem(value)); - break; - case MetaData.SAVE_ORDER_CONFIG: - metaData.setSaveOrderConfig(SaveOrderConfig.parse(value)); - break; - case "groupsversion": - case "groups": - // These keys were used in previous JabRef versions, we will not support them anymore -> ignored - break; - + case MetaData.GROUPSTREE: + case MetaData.GROUPSTREE_LEGACY: + metaData.setGroups(GroupsParser.importGroups(value, keywordSeparator)); + break; + case MetaData.SAVE_ACTIONS: + metaData.setSaveActions(Cleanups.parse(value)); + break; + case MetaData.DATABASE_TYPE: + metaData.setMode(BibDatabaseMode.parse(getSingleItem(value))); + break; + case MetaData.KEYPATTERNDEFAULT: + defaultCiteKeyPattern = Collections.singletonList(getSingleItem(value)); + break; + case MetaData.PROTECTED_FLAG_META: + if (Boolean.parseBoolean(getSingleItem(value))) { + metaData.markAsProtected(); + } else { + metaData.markAsNotProtected(); + } + break; + case MetaData.FILE_DIRECTORY: + metaData.setDefaultFileDirectory(getSingleItem(value)); + break; + case MetaData.SAVE_ORDER_CONFIG: + metaData.setSaveOrderConfig(SaveOrderConfig.parse(value)); + break; + case "groupsversion": + case "groups": + // These keys were used in previous JabRef versions, we will not support them anymore -> ignored + break; } } if (!defaultCiteKeyPattern.isEmpty() || !nonDefaultCiteKeyPatterns.isEmpty()) { diff --git a/src/main/java/org/jabref/logic/util/BuildInfo.java b/src/main/java/org/jabref/logic/util/BuildInfo.java index cdefe91d88e..c414fd3ace4 100644 --- a/src/main/java/org/jabref/logic/util/BuildInfo.java +++ b/src/main/java/org/jabref/logic/util/BuildInfo.java @@ -20,6 +20,7 @@ public class BuildInfo { private final String authors; private final String developers; private final String year; + private final String azureInstrumentationKey; public BuildInfo() { @@ -43,7 +44,7 @@ public BuildInfo(String path) { authors = properties.getProperty("authors", ""); year = properties.getProperty("year", ""); developers = properties.getProperty("developers", ""); - + azureInstrumentationKey = properties.getProperty("azureInstrumentationKey", ""); } public Version getVersion() { @@ -62,4 +63,7 @@ public String getYear() { return year; } + public String getAzureInstrumentationKey() { + return azureInstrumentationKey; + } } diff --git a/src/main/java/org/jabref/model/ChainNode.java b/src/main/java/org/jabref/model/ChainNode.java index d28f3253794..1423ebefb96 100644 --- a/src/main/java/org/jabref/model/ChainNode.java +++ b/src/main/java/org/jabref/model/ChainNode.java @@ -58,35 +58,24 @@ public Optional getParent() { } /** - * Returns this node's child or an empty Optional if this node has no child. + * Sets the parent node of this node. + *

+ * This method does not set this node as the child of the new parent nor does it remove this node + * from the old parent. You should probably call {@link #moveTo(ChainNode)} to change the chain. * - * @return this node's child T, or an empty Optional if this node has no child + * @param parent the new parent */ - public Optional getChild() { - return Optional.ofNullable(child); + protected void setParent(T parent) { + this.parent = Objects.requireNonNull(parent); } /** - * Removes this node from its parent and makes it a child of the specified node. - * In this way the whole subchain based at this node is moved to the given node. + * Returns this node's child or an empty Optional if this node has no child. * - * @param target the new parent - * @throws NullPointerException if target is null - * @throws UnsupportedOperationException if target is an descendant of this node + * @return this node's child T, or an empty Optional if this node has no child */ - public void moveTo(T target) { - Objects.requireNonNull(target); - - // Check that the target node is not an ancestor of this node, because this would create loops in the tree - if (this.isAncestorOf(target)) { - throw new UnsupportedOperationException("the target cannot be a descendant of this node"); - } - - // Remove from previous parent - getParent().ifPresent(oldParent -> oldParent.removeChild()); - - // Add as child - target.setChild((T) this); + public Optional getChild() { + return Optional.ofNullable(child); } /** @@ -110,15 +99,26 @@ public T setChild(T child) { } /** - * Sets the parent node of this node. - *

- * This method does not set this node as the child of the new parent nor does it remove this node - * from the old parent. You should probably call {@link #moveTo(ChainNode)} to change the chain. + * Removes this node from its parent and makes it a child of the specified node. + * In this way the whole subchain based at this node is moved to the given node. * - * @param parent the new parent + * @param target the new parent + * @throws NullPointerException if target is null + * @throws UnsupportedOperationException if target is an descendant of this node */ - protected void setParent(T parent) { - this.parent = Objects.requireNonNull(parent); + public void moveTo(T target) { + Objects.requireNonNull(target); + + // Check that the target node is not an ancestor of this node, because this would create loops in the tree + if (this.isAncestorOf(target)) { + throw new UnsupportedOperationException("the target cannot be a descendant of this node"); + } + + // Remove from previous parent + getParent().ifPresent(oldParent -> oldParent.removeChild()); + + // Add as child + target.setChild((T) this); } /** @@ -151,4 +151,16 @@ public boolean isAncestorOf(T anotherNode) { return child.isAncestorOf(anotherNode); } } + + /** + * Adds the given node at the end of the chain. + * E.g., "A > B > C" + "D" -> "A > B > C > D". + */ + public void addAtEnd(T node) { + if (child == null) { + setChild(node); + } else { + child.addAtEnd(node); + } + } } diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index 5ce9bbb7b52..6bd04c79a28 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -3,6 +3,7 @@ import java.math.BigInteger; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -198,6 +199,10 @@ public synchronized boolean insertEntry(BibEntry entry, EntryEventSource eventSo return duplicationChecker.isDuplicateCiteKeyExisting(entry); } + public synchronized void insertEntries(BibEntry... entries) throws KeyCollisionException { + insertEntries(Arrays.asList(entries), EntryEventSource.LOCAL); + } + public synchronized void insertEntries(List entries) throws KeyCollisionException { insertEntries(entries, EntryEventSource.LOCAL); } diff --git a/src/main/java/org/jabref/model/entry/Keyword.java b/src/main/java/org/jabref/model/entry/Keyword.java index e21c647318a..b01e3f398cd 100644 --- a/src/main/java/org/jabref/model/entry/Keyword.java +++ b/src/main/java/org/jabref/model/entry/Keyword.java @@ -1,8 +1,12 @@ package org.jabref.model.entry; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jabref.model.ChainNode; +import org.jabref.model.util.OptionalUtil; /** * Represents a keyword in a chain of keywords. @@ -10,11 +14,28 @@ */ public class Keyword extends ChainNode implements Comparable { + public static Character DEFAULT_HIERARCHICAL_DELIMITER = '>'; private final String keyword; public Keyword(String keyword) { super(Keyword.class); - this.keyword = Objects.requireNonNull(keyword); + this.keyword = Objects.requireNonNull(keyword).trim(); + } + + /** + * Connects all the given keywords into one chain and returns its root, + * e.g. "A", "B", "C" is transformed into "A > B > C". + */ + public static Keyword of(String... keywords) { + if (keywords.length == 0) { + return new Keyword(""); + } + + Keyword root = new Keyword(keywords[0]); + for (int i = 1; i < keywords.length; i++) { + root.addAtEnd(keywords[i]); + } + return root; } @Override @@ -25,7 +46,10 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - return Objects.equals(keyword, ((Keyword) o).keyword); + Keyword other = (Keyword) o; + return Objects.equals(this.keyword, other.keyword) + // && Objects.equals(this.getParent(), other.getParent()) : we can't check the parents because then we would run in circles + && Objects.equals(this.getChild(), other.getChild()); } @Override @@ -35,11 +59,68 @@ public int hashCode() { @Override public String toString() { - return keyword; + return getSubchainAsString(DEFAULT_HIERARCHICAL_DELIMITER); } @Override public int compareTo(Keyword o) { return keyword.compareTo(o.keyword); } + + /** + * Adds the given keyword at the end of the chain. + * E.g., "A > B > C" + "D" -> "A > B > C > D". + */ + private void addAtEnd(String keyword) { + addAtEnd(new Keyword(keyword)); + } + + /** + * Returns a text representation of the subchain starting at this item. + * E.g., calling {@link #getSubchainAsString(Character)} on the node "B" in "A > B > C" returns "B > C". + */ + private String getSubchainAsString(Character hierarchicalDelimiter) { + return keyword + + getChild().map(child -> " " + hierarchicalDelimiter + " " + child.getSubchainAsString(hierarchicalDelimiter)) + .orElse(""); + } + + /** + * Gets the keyword of this node in the chain. + */ + public String get() { + return keyword; + } + + /** + * Returns a text representation of the path from the root to this item. + * E.g., calling {@link #getPathFromRootAsString(Character)} on the node "B" in "A > B > C" returns "A > B". + */ + public String getPathFromRootAsString(Character hierarchicalDelimiter) { + return getParent() + .map(parent -> parent.getPathFromRootAsString(hierarchicalDelimiter) + " " + hierarchicalDelimiter + " ") + .orElse("") + + keyword; + } + + /** + * Returns all nodes in this chain as separate keywords. + * E.g, for "A > B > C" we get {"A", "B", "C"}. + */ + public Set flatten() { + return Stream.concat( + Stream.of(this), + OptionalUtil.toStream(getChild()).flatMap(child -> child.flatten().stream())) + .collect(Collectors.toSet()); + } + + /** + * Returns all subchains starting at this node. + * E.g., for the chain "A > B > C" the subchains {"A", "A > B", "A > B > C"} are returned. + */ + public Set getAllSubchainsAsString(Character hierarchicalDelimiter) { + return flatten().stream() + .map(subchain -> subchain.getPathFromRootAsString(hierarchicalDelimiter)) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/org/jabref/model/entry/KeywordList.java b/src/main/java/org/jabref/model/entry/KeywordList.java index fad91d2379e..c9e435a762f 100644 --- a/src/main/java/org/jabref/model/entry/KeywordList.java +++ b/src/main/java/org/jabref/model/entry/KeywordList.java @@ -19,58 +19,67 @@ */ public class KeywordList implements Iterable { - private final List keywords; + private final List keywordChains; public KeywordList() { - keywords = new ArrayList<>(); + keywordChains = new ArrayList<>(); } - public KeywordList(Collection keywords) { - this.keywords = new ArrayList<>(); - keywords.forEach(this::add); + public KeywordList(Collection keywordChains) { + this.keywordChains = new ArrayList<>(); + keywordChains.forEach(this::add); } - public KeywordList(List keywords) { - this(keywords.stream().map(Keyword::new).collect(Collectors.toList())); + public KeywordList(List keywordChains) { + this(keywordChains.stream().map(Keyword::new).collect(Collectors.toList())); } - public KeywordList(String... keywords) { - this(Arrays.stream(keywords).map(Keyword::new).collect(Collectors.toList())); + public KeywordList(String... keywordChains) { + this(Arrays.stream(keywordChains).map(Keyword::new).collect(Collectors.toList())); } - /** - * @param keywordString a String of keywords - * @return an parsed list containing the keywords - */ - public static KeywordList parse(String keywordString, Character delimiter) { + public KeywordList(Keyword... keywordChains) { + this(Arrays.asList(keywordChains)); + } + + public static KeywordList parse(String keywordString, Character delimiter, Character hierarchicalDelimiter) { if (StringUtil.isBlank(keywordString)) { return new KeywordList(); } - List keywords = new ArrayList<>(); + KeywordList keywordList = new KeywordList(); StringTokenizer tok = new StringTokenizer(keywordString, delimiter.toString()); while (tok.hasMoreTokens()) { - String word = tok.nextToken().trim(); - keywords.add(word); + String chain = tok.nextToken(); + Keyword chainRoot = Keyword.of(chain.split(hierarchicalDelimiter.toString())); + keywordList.add(chainRoot); } - return new KeywordList(keywords); + return keywordList; + } + + /** + * @param keywordString a String of keywordChains + * @return an parsed list containing the keywordChains + */ + public static KeywordList parse(String keywordString, Character delimiter) { + return parse(keywordString, delimiter, Keyword.DEFAULT_HIERARCHICAL_DELIMITER); } public KeywordList createClone() { - return new KeywordList(this.keywords); + return new KeywordList(this.keywordChains); } public void replaceAll(KeywordList keywordsToReplace, Keyword newValue) { Objects.requireNonNull(newValue); - // Remove keywords which should be replaced + // Remove keywordChains which should be replaced int foundPosition = -1; // remember position of the last found keyword for (Keyword specialFieldKeyword : keywordsToReplace) { - int pos = keywords.indexOf(specialFieldKeyword); + int pos = keywordChains.indexOf(specialFieldKeyword); if (pos >= 0) { foundPosition = pos; - keywords.remove(pos); + keywordChains.remove(pos); } } @@ -78,26 +87,26 @@ public void replaceAll(KeywordList keywordsToReplace, Keyword newValue) { if (foundPosition == -1) { add(newValue); } else { - keywords.add(foundPosition, newValue); + keywordChains.add(foundPosition, newValue); } } public void removeAll(KeywordList keywordsToRemove) { - keywords.removeAll(keywordsToRemove.keywords); + keywordChains.removeAll(keywordsToRemove.keywordChains); } public boolean add(Keyword keyword) { if (contains(keyword)) { - return false; // Don't add duplicate keywords + return false; // Don't add duplicate keywordChains } - return keywords.add(keyword); + return keywordChains.add(keyword); } /** * Keywords are separated by the given delimiter and an additional space, i.e. "one, two". */ public String getAsString(Character delimiter) { - return keywords.stream().map(Keyword::toString).collect(Collectors.joining(delimiter + " ")); + return keywordChains.stream().map(Keyword::toString).collect(Collectors.joining(delimiter + " ")); } public void add(String keywordsString) { @@ -106,47 +115,47 @@ public void add(String keywordsString) { @Override public Iterator iterator() { - return keywords.iterator(); + return keywordChains.iterator(); } public int size() { - return keywords.size(); + return keywordChains.size(); } public boolean isEmpty() { - return keywords.isEmpty(); + return keywordChains.isEmpty(); } public boolean contains(Keyword o) { - return keywords.contains(o); + return keywordChains.contains(o); } public boolean remove(Keyword o) { - return keywords.remove(o); + return keywordChains.remove(o); } public boolean remove(String keywordsString) { - return keywords.remove(new Keyword(keywordsString)); + return keywordChains.remove(new Keyword(keywordsString)); } public void addAll(KeywordList keywordsToAdd) { - keywords.addAll(keywordsToAdd.keywords); + keywordChains.addAll(keywordsToAdd.keywordChains); } public void retainAll(KeywordList keywordToRetain) { - keywords.retainAll(keywordToRetain.keywords); + keywordChains.retainAll(keywordToRetain.keywordChains); } public void clear() { - keywords.clear(); + keywordChains.clear(); } public Keyword get(int index) { - return keywords.get(index); + return keywordChains.get(index); } public Stream stream() { - return keywords.stream(); + return keywordChains.stream(); } @Override @@ -155,7 +164,7 @@ public String toString() { } public Set toStringList() { - return keywords.stream().map(Keyword::toString).collect(Collectors.toSet()); + return keywordChains.stream().map(Keyword::toString).collect(Collectors.toSet()); } @Override @@ -167,11 +176,11 @@ public boolean equals(Object o) { return false; } KeywordList keywords1 = (KeywordList) o; - return Objects.equals(keywords, keywords1.keywords); + return Objects.equals(keywordChains, keywords1.keywordChains); } @Override public int hashCode() { - return Objects.hash(keywords); + return Objects.hash(keywordChains); } } diff --git a/src/main/java/org/jabref/model/groups/AutomaticGroup.java b/src/main/java/org/jabref/model/groups/AutomaticGroup.java index 62d619fa5cf..261a5691b48 100644 --- a/src/main/java/org/jabref/model/groups/AutomaticGroup.java +++ b/src/main/java/org/jabref/model/groups/AutomaticGroup.java @@ -2,7 +2,10 @@ import java.util.Set; +import javafx.collections.ObservableList; + import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.TreeCollector; public abstract class AutomaticGroup extends AbstractGroup { public AutomaticGroup(String name, GroupHierarchyType context) { @@ -20,4 +23,11 @@ public boolean isDynamic() { } public abstract Set createSubgroups(BibEntry entry); + + public ObservableList createSubgroups(ObservableList entries) { + // TODO: Propagate changes to entry list (however: there is no flatMap and collect as TransformationList) + return entries.stream() + .flatMap(entry -> createSubgroups(entry).stream()) + .collect(TreeCollector.mergeIntoTree(GroupTreeNode::isSameGroupAs)); + } } diff --git a/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java index ea95b545770..062648d7f72 100644 --- a/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java +++ b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java @@ -6,22 +6,29 @@ import java.util.stream.Collectors; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.Keyword; import org.jabref.model.entry.KeywordList; import org.jabref.model.util.OptionalUtil; public class AutomaticKeywordGroup extends AutomaticGroup { - private Character keywordSeperator; + private Character keywordDelimiter; + private Character keywordHierarchicalDelimiter; private String field; - public AutomaticKeywordGroup(String name, GroupHierarchyType context, String field, Character keywordSeperator) { + public AutomaticKeywordGroup(String name, GroupHierarchyType context, String field, Character keywordDelimiter, Character keywordHierarchicalDelimiter) { super(name, context); this.field = field; - this.keywordSeperator = keywordSeperator; + this.keywordDelimiter = keywordDelimiter; + this.keywordHierarchicalDelimiter = keywordHierarchicalDelimiter; } - public Character getKeywordSeperator() { - return keywordSeperator; + public Character getKeywordHierarchicalDelimiter() { + return keywordHierarchicalDelimiter; + } + + public Character getKeywordDelimiter() { + return keywordDelimiter; } public String getField() { @@ -30,7 +37,7 @@ public String getField() { @Override public AbstractGroup deepCopy() { - return new AutomaticKeywordGroup(this.name, this.context, field, this.keywordSeperator); + return new AutomaticKeywordGroup(this.name, this.context, field, this.keywordDelimiter, keywordHierarchicalDelimiter); } @Override @@ -38,22 +45,38 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AutomaticKeywordGroup that = (AutomaticKeywordGroup) o; - return Objects.equals(keywordSeperator, that.keywordSeperator) && + return Objects.equals(keywordDelimiter, that.keywordDelimiter) && Objects.equals(field, that.field); } @Override public int hashCode() { - return Objects.hash(keywordSeperator, field); + return Objects.hash(keywordDelimiter, field); } @Override public Set createSubgroups(BibEntry entry) { Optional keywordList = entry.getLatexFreeField(field) - .map(fieldValue -> KeywordList.parse(fieldValue, keywordSeperator)); - return OptionalUtil.flatMap(keywordList, KeywordList::toStringList) - .map(keyword -> new WordKeywordGroup(keyword, GroupHierarchyType.INDEPENDENT, field, keyword, true, keywordSeperator, true)) - .map(GroupTreeNode::new) + .map(fieldValue -> KeywordList.parse(fieldValue, keywordDelimiter)); + return OptionalUtil.toStream(keywordList) + .flatMap(KeywordList::stream) + .map(this::createGroup) .collect(Collectors.toSet()); } + + private GroupTreeNode createGroup(Keyword keywordChain) { + WordKeywordGroup rootGroup = new WordKeywordGroup( + keywordChain.get(), + GroupHierarchyType.INCLUDING, + field, + keywordChain.getPathFromRootAsString(keywordHierarchicalDelimiter), + true, + keywordDelimiter, + true); + GroupTreeNode root = new GroupTreeNode(rootGroup); + keywordChain.getChild() + .map(this::createGroup) + .ifPresent(root::addChild); + return root; + } } diff --git a/src/main/java/org/jabref/model/groups/GroupTreeNode.java b/src/main/java/org/jabref/model/groups/GroupTreeNode.java index d75bcede4a6..9b16be98f38 100644 --- a/src/main/java/org/jabref/model/groups/GroupTreeNode.java +++ b/src/main/java/org/jabref/model/groups/GroupTreeNode.java @@ -130,7 +130,8 @@ public boolean equals(Object o) { return false; } GroupTreeNode that = (GroupTreeNode) o; - return Objects.equals(group, that.group); + return Objects.equals(group, that.group) && + Objects.equals(getChildren(), that.getChildren()); } @Override @@ -294,4 +295,11 @@ public List removeEntriesFromGroup(List entries) { return Collections.emptyList(); } } + + /** + * Returns true if the underlying groups of both {@link GroupTreeNode}s is the same. + */ + public boolean isSameGroupAs(GroupTreeNode other) { + return Objects.equals(group, other.group); + } } diff --git a/src/main/java/org/jabref/model/metadata/MetaData.java b/src/main/java/org/jabref/model/metadata/MetaData.java index 876f3e654d6..35a10a5d6d7 100644 --- a/src/main/java/org/jabref/model/metadata/MetaData.java +++ b/src/main/java/org/jabref/model/metadata/MetaData.java @@ -29,7 +29,8 @@ public class MetaData { public static final String PREFIX_KEYPATTERN = "keypattern_"; public static final String KEYPATTERNDEFAULT = "keypatterndefault"; public static final String DATABASE_TYPE = "databaseType"; - public static final String GROUPSTREE = "groupstree"; + public static final String GROUPSTREE = "grouping"; + public static final String GROUPSTREE_LEGACY = "groupstree"; public static final String FILE_DIRECTORY = FieldName.FILE + FileDirectoryPreferences.DIR_SUFFIX; public static final String PROTECTED_FLAG_META = "protectedFlag"; public static final String SELECTOR_META_PREFIX = "selector_"; @@ -39,11 +40,11 @@ public class MetaData { public static final String SEPARATOR_STRING = String.valueOf(SEPARATOR_CHARACTER); private final EventBus eventBus = new EventBus(); + private final Map citeKeyPatterns = new HashMap<>(); // + private final Map userFileDirectory = new HashMap<>(); // private GroupTreeNode groupsRoot; private Charset encoding; private SaveOrderConfig saveOrderConfig; - private final Map citeKeyPatterns = new HashMap<>(); // - private final Map userFileDirectory = new HashMap<>(); // private String defaultCiteKeyPattern; private FieldFormatterCleanups saveActions; private BibDatabaseMode mode; diff --git a/src/main/java/org/jabref/model/strings/LatexToUnicodeAdapter.java b/src/main/java/org/jabref/model/strings/LatexToUnicodeAdapter.java index ecdfd8489d0..0f7d612a18a 100644 --- a/src/main/java/org/jabref/model/strings/LatexToUnicodeAdapter.java +++ b/src/main/java/org/jabref/model/strings/LatexToUnicodeAdapter.java @@ -2,6 +2,7 @@ import java.text.Normalizer; import java.util.Objects; +import java.util.regex.Pattern; import com.github.tomtung.latex2unicode.LaTeX2Unicode; @@ -10,9 +11,17 @@ */ public class LatexToUnicodeAdapter { + private static Pattern underscoreMatcher = Pattern.compile("_(?!\\{)"); + + private static String replacementChar = "\uFFFD"; + + private static Pattern underscorePlaceholderMatcher = Pattern.compile(replacementChar); + public static String format(String inField) { Objects.requireNonNull(inField); - return Normalizer.normalize(LaTeX2Unicode.convert(inField), Normalizer.Form.NFKC); + String toFormat = underscoreMatcher.matcher(inField).replaceAll(replacementChar); + toFormat = Normalizer.normalize(LaTeX2Unicode.convert(toFormat), Normalizer.Form.NFKC); + return underscorePlaceholderMatcher.matcher(toFormat).replaceAll("_"); } } diff --git a/src/main/java/org/jabref/model/util/TreeCollector.java b/src/main/java/org/jabref/model/util/TreeCollector.java new file mode 100644 index 00000000000..2955774bc1f --- /dev/null +++ b/src/main/java/org/jabref/model/util/TreeCollector.java @@ -0,0 +1,104 @@ +package org.jabref.model.util; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.model.TreeNode; + +/** + * Merges a list of nodes into a tree. + * Nodes with a common parent are added as direct children. + * For example, the list { A > A1, A > A2, B } is transformed into the forest { A > A1, A2, B}. + */ +public class TreeCollector implements Collector, ObservableList> { + + private Function> getChildren; + private BiConsumer addChild; + private BiPredicate equivalence; + + /** + * @param getChildren a function that returns a list of children of the specified node + * @param addChild a function that adds the second argument as a child to the first-specified node + * @param equivalence a function that tells us whether two nodes are equivalent + */ + private TreeCollector(Function> getChildren, BiConsumer addChild, BiPredicate equivalence) { + this.getChildren = getChildren; + this.addChild = addChild; + this.equivalence = equivalence; + } + + public static > TreeCollector mergeIntoTree(BiPredicate equivalence) { + return new TreeCollector( + TreeNode::getChildren, + (parent, child) -> child.moveTo(parent), + equivalence); + } + + @Override + public Supplier> supplier() { + return FXCollections::observableArrayList; + } + + @Override + public BiConsumer, T> accumulator() { + return (alreadyProcessed, newItem) -> { + // Check if the node is already in the tree + Optional sameItemInTree = alreadyProcessed.stream() + .filter(item -> equivalence.test(item, newItem)) + .findFirst(); + if (sameItemInTree.isPresent()) { + for (T child : new ArrayList<>(getChildren.apply(newItem))) { + merge(sameItemInTree.get(), child); + } + } else { + alreadyProcessed.add(newItem); + } + }; + } + + private void merge(T target, T node) { + Optional sameItemInTree = getChildren.apply(target).stream() + .filter(item -> equivalence.test(item, node)) + .findFirst(); + if (sameItemInTree.isPresent()) { + // We need to copy the list because the #addChild method might remove the child from its own parent + for (T child : new ArrayList<>(getChildren.apply(node))) { + merge(sameItemInTree.get(), child); + } + } else { + addChild.accept(target, node); + } + } + + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + for (T item : list2) { + accumulator().accept(list1, item); + } + return list1; + }; + } + + @Override + public Function, ObservableList> finisher() { + return i -> i; + } + + @Override + public Set characteristics() { + return EnumSet.of(Characteristics.UNORDERED, Characteristics.IDENTITY_FINISH); + } +} diff --git a/src/main/java/org/jabref/pdfimport/ImportDialog.java b/src/main/java/org/jabref/pdfimport/ImportDialog.java index f39681d773a..f47db08fee4 100644 --- a/src/main/java/org/jabref/pdfimport/ImportDialog.java +++ b/src/main/java/org/jabref/pdfimport/ImportDialog.java @@ -18,7 +18,6 @@ import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; -import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -28,6 +27,7 @@ import javax.swing.WindowConstants; import org.jabref.Globals; +import org.jabref.gui.JabRefDialog; import org.jabref.logic.l10n.Localization; import org.jabref.logic.xmp.XMPUtil; import org.jabref.model.entry.BibEntry; @@ -38,7 +38,7 @@ import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.FormLayout; -public class ImportDialog extends JDialog { +public class ImportDialog extends JabRefDialog { public static final int NOMETA = 0; public static final int XMP = 1; @@ -55,6 +55,8 @@ public class ImportDialog extends JDialog { public ImportDialog(boolean targetIsARow, String fileName) { + super(ImportDialog.class); + Boolean targetIsARow1 = targetIsARow; JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index cda4f46e9de..48ac595fc42 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -25,6 +25,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.prefs.BackingStoreException; import java.util.prefs.InvalidPreferencesFormatException; import java.util.prefs.Preferences; @@ -359,6 +360,13 @@ public class JabRefPreferences { public static final String CUSTOMIZED_BIBLATEX_TYPES = "customizedBiblatexTypes"; // Version public static final String VERSION_IGNORED_UPDATE = "versionIgnoreUpdate"; + // User + private static final String USER_ID = "userId"; + + // Telemetry collection + private static final String COLLECT_TELEMETRY = "collectTelemetry"; + private static final String ALREADY_ASKED_TO_COLLECT_TELEMETRY = "askedCollectTelemetry"; + // Dropped file handler public static final String DROPPEDFILEHANDLER_RENAME = "DroppedFileHandler_RenameFile"; public static final String DROPPEDFILEHANDLER_MOVE = "DroppedFileHandler_MoveFile"; @@ -714,6 +722,8 @@ private JabRefPreferences() { defaults.put(DB_CONNECT_HOSTNAME, "localhost"); defaults.put(DB_CONNECT_DATABASE, "jabref"); defaults.put(DB_CONNECT_USERNAME, "root"); + defaults.put(COLLECT_TELEMETRY, Boolean.FALSE); + defaults.put(ALREADY_ASKED_TO_COLLECT_TELEMETRY, Boolean.FALSE); defaults.put(ASK_AUTO_NAMING_PDFS_AGAIN, Boolean.TRUE); insertDefaultCleanupPreset(defaults); @@ -1539,4 +1549,31 @@ public SaveOrderConfig loadExportSaveOrder() { public Character getKeywordDelimiter() { return get(KEYWORD_SEPARATOR).charAt(0); } + + public String getOrCreateUserId() { + Optional userId = getAsOptional(USER_ID); + if (userId.isPresent()) { + return userId.get(); + } else { + String newUserId = UUID.randomUUID().toString(); + put(USER_ID, newUserId); + return newUserId; + } + } + + public Boolean shouldCollectTelemetry() { + return getBoolean(COLLECT_TELEMETRY); + } + + public void setShouldCollectTelemetry(boolean value) { + putBoolean(COLLECT_TELEMETRY, value); + } + + public Boolean shouldAskToCollectTelemetry() { + return getBoolean(ALREADY_ASKED_TO_COLLECT_TELEMETRY); + } + + public void askedToCollectTelemetry() { + putBoolean(ALREADY_ASKED_TO_COLLECT_TELEMETRY, true); + } } diff --git a/src/main/resources/ApplicationInsights.xml b/src/main/resources/ApplicationInsights.xml new file mode 100644 index 00000000000..e6de102ea18 --- /dev/null +++ b/src/main/resources/ApplicationInsights.xml @@ -0,0 +1,23 @@ + + + + + + False + + + + + + + + true + + diff --git a/src/main/resources/build.properties b/src/main/resources/build.properties index 728f85eb6b2..e2dba37b007 100644 --- a/src/main/resources/build.properties +++ b/src/main/resources/build.properties @@ -2,3 +2,4 @@ version=${version} year=${year} authors=${authors} developers=${developers} +azureInstrumentationKey=${azureInstrumentationKey} diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 7b70baf3b75..0e07a7debee 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 5feb04acdf8..9a93d6c8e21 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index e3b80248b44..4aefbd6fc52 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2348,3 +2348,8 @@ Color=Color Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.=Please_also_add_all_steps_to_reproduce_this_issue,_if_possible. Sort_all_subgroups_(recursively)=Sort_all_subgroups_(recursively) +Collect_and_share_telemetry_data_to_help_improve_JabRef.=Collect_and_share_telemetry_data_to_help_improve_JabRef. +Don't_share=Don't_share +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. diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 7400a04a140..32afe2dbf8b 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index 6b09a0dd8df..0d6ed832b81 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 8ccb16c388d..688ad98ed50 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -2348,3 +2348,8 @@ Color=Couleur Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.=S'il_vous_plaît,_Ajouter_si_possible_toutes_les_étapes_permettant_de_reproduire_cette_anomalie. Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 1de13e2260f..b2e09b1a022 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index f6b0eaff43b..11100b29ff7 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 3483866005c..6652f4cc4bd 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index aed7a2d2233..40e2a48e813 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index fed5a986c73..5c4416943dd 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index d0ecaeaf91e..0cebf8cec4b 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 9223b86c700..d2aaf426122 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index 8f2121c866e..46ebec083b4 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index b892dca2986..2828abf2b8f 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 4cf9606c390..6b0079b413d 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index 5624511ab31..6a21a4e4189 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -2348,3 +2348,8 @@ Color= Please_also_add_all_steps_to_reproduce_this_issue,_if_possible.= Sort_all_subgroups_(recursively)= +Collect_and_share_telemetry_data_to_help_improve_JabRef.= +Don't_share= +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.= diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 7e7d267c346..a8e5ddd550b 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -7,6 +7,7 @@ + @@ -14,6 +15,7 @@ + diff --git a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java index 281cf995790..cb8801ed880 100644 --- a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java +++ b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java @@ -1,13 +1,17 @@ package org.jabref.gui.groups; import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import org.jabref.gui.StateManager; import org.jabref.gui.util.CurrentThreadTaskExecutor; import org.jabref.gui.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.AbstractGroup; +import org.jabref.model.groups.AutomaticKeywordGroup; import org.jabref.model.groups.GroupHierarchyType; +import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.groups.WordKeywordGroup; import org.junit.Before; @@ -54,6 +58,35 @@ public void isMatchedIfContainsPartOfSearchString() throws Exception { assertTrue(viewModel.isMatchedBy("est")); } + @Test + public void treeOfAutomaticKeywordGroupIsCombined() throws Exception { + BibEntry entryOne = new BibEntry().withField("keywords", "A > B > B1, A > C"); + BibEntry entryTwo = new BibEntry().withField("keywords", "A > D, E"); + BibEntry entryThree = new BibEntry().withField("keywords", "A > B > B2"); + databaseContext.getDatabase().insertEntries(entryOne, entryTwo, entryThree); + + AutomaticKeywordGroup group = new AutomaticKeywordGroup("Keywords", GroupHierarchyType.INDEPENDENT, "keywords", ',', '>'); + GroupNodeViewModel groupViewModel = getViewModelForGroup(group); + + WordKeywordGroup expectedGroupA = new WordKeywordGroup("A", GroupHierarchyType.INCLUDING, "keywords", "A", true, ',', true); + WordKeywordGroup expectedGroupB = new WordKeywordGroup("B", GroupHierarchyType.INCLUDING, "keywords", "A > B", true, ',', true); + WordKeywordGroup expectedGroupB1 = new WordKeywordGroup("B1", GroupHierarchyType.INCLUDING, "keywords", "A > B > B1", true, ',', true); + WordKeywordGroup expectedGroupB2 = new WordKeywordGroup("B2", GroupHierarchyType.INCLUDING, "keywords", "A > B > B2", true, ',', true); + WordKeywordGroup expectedGroupC = new WordKeywordGroup("C", GroupHierarchyType.INCLUDING, "keywords", "A > C", true, ',', true); + WordKeywordGroup expectedGroupD = new WordKeywordGroup("D", GroupHierarchyType.INCLUDING, "keywords", "A > D", true, ',', true); + WordKeywordGroup expectedGroupE = new WordKeywordGroup("E", GroupHierarchyType.INCLUDING, "keywords", "E", true, ',', true); + GroupNodeViewModel expectedA = getViewModelForGroup(expectedGroupA); + GroupTreeNode expectedB = expectedA.addSubgroup(expectedGroupB); + expectedB.addSubgroup(expectedGroupB1); + expectedB.addSubgroup(expectedGroupB2); + expectedA.addSubgroup(expectedGroupC); + expectedA.addSubgroup(expectedGroupD); + GroupNodeViewModel expectedE = getViewModelForGroup(expectedGroupE); + ObservableList expected = FXCollections.observableArrayList(expectedA, expectedE); + + assertEquals(expected, groupViewModel.getChildren()); + } + private GroupNodeViewModel getViewModelForGroup(AbstractGroup group) { return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group); } diff --git a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java index f9a5ebd70bb..e48221ce53e 100644 --- a/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java +++ b/src/test/java/org/jabref/logic/exporter/BibtexDatabaseWriterTest.java @@ -194,7 +194,7 @@ public void writeGroups() throws Exception { // @formatter:off assertEquals(OS.NEWLINE - + "@Comment{jabref-meta: groupstree:" + OS.NEWLINE + + "@Comment{jabref-meta: grouping:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE + "1 StaticGroup:test\\;2\\;1\\;\\;\\;\\;;" + OS.NEWLINE + "}" + OS.NEWLINE, session.getStringValue()); @@ -215,7 +215,7 @@ public void writeGroupsAndEncoding() throws Exception { assertEquals( "% Encoding: US-ASCII" + OS.NEWLINE + OS.NEWLINE - + "@Comment{jabref-meta: groupstree:" + OS.NEWLINE + + "@Comment{jabref-meta: grouping:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE + "1 StaticGroup:test\\;2\\;1\\;\\;\\;\\;;" + OS.NEWLINE + "}" + OS.NEWLINE, session.getStringValue()); diff --git a/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java b/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java index 626d7e2b7c0..c74c63ea41c 100644 --- a/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java +++ b/src/test/java/org/jabref/logic/exporter/GroupSerializerTest.java @@ -96,9 +96,9 @@ public void serializeSingleSearchGroupWithRegex() { @Test public void serializeSingleAutomaticKeywordGroup() { - AutomaticGroup group = new AutomaticKeywordGroup("myAutomaticGroup", GroupHierarchyType.INDEPENDENT, "keywords", ','); + AutomaticGroup group = new AutomaticKeywordGroup("myAutomaticGroup", GroupHierarchyType.INDEPENDENT, "keywords", ',', '>'); List serialization = groupSerializer.serializeTree(GroupTreeNode.fromGroup(group)); - assertEquals(Collections.singletonList("0 AutomaticKeywordGroup:myAutomaticGroup;0;keywords;,;1;;;;"), serialization); + assertEquals(Collections.singletonList("0 AutomaticKeywordGroup:myAutomaticGroup;0;keywords;,;>;1;;;;"), serialization); } @Test diff --git a/src/test/java/org/jabref/logic/importer/DatabaseFileLookupTest.java b/src/test/java/org/jabref/logic/importer/DatabaseFileLookupTest.java index 433776c2088..44ba9f9d533 100644 --- a/src/test/java/org/jabref/logic/importer/DatabaseFileLookupTest.java +++ b/src/test/java/org/jabref/logic/importer/DatabaseFileLookupTest.java @@ -1,8 +1,6 @@ package org.jabref.logic.importer; import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -30,7 +28,7 @@ public class DatabaseFileLookupTest { @Before - public void setUp() throws FileNotFoundException, IOException { + public void setUp() throws Exception { try (FileInputStream stream = new FileInputStream(ImportDataTest.UNLINKED_FILES_TEST_BIB); InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) { ParserResult result = new BibtexParser(JabRefPreferences.getInstance().getImportFormatPreferences()).parse(reader); diff --git a/src/test/java/org/jabref/logic/importer/util/GroupsParserTest.java b/src/test/java/org/jabref/logic/importer/util/GroupsParserTest.java index 4e69fb5d223..56a9d2f14e1 100644 --- a/src/test/java/org/jabref/logic/importer/util/GroupsParserTest.java +++ b/src/test/java/org/jabref/logic/importer/util/GroupsParserTest.java @@ -76,8 +76,8 @@ public void fromStringParsesExplicitGroupWithIconAndDesrcitpion() throws Excepti @Test public void fromStringParsesAutomaticKeywordGroup() throws Exception { - AutomaticGroup expected = new AutomaticKeywordGroup("myAutomaticGroup", GroupHierarchyType.INDEPENDENT, "keywords", ','); - AbstractGroup parsed = GroupsParser.fromString("AutomaticKeywordGroup:myAutomaticGroup;0;keywords;,;1;;;;", ','); + AutomaticGroup expected = new AutomaticKeywordGroup("myAutomaticGroup", GroupHierarchyType.INDEPENDENT, "keywords", ',', '>'); + AbstractGroup parsed = GroupsParser.fromString("AutomaticKeywordGroup:myAutomaticGroup;0;keywords;,;>;1;;;;", ','); assertEquals(expected, parsed); } diff --git a/src/test/java/org/jabref/logic/layout/format/LatexToUnicodeFormatterTest.java b/src/test/java/org/jabref/logic/layout/format/LatexToUnicodeFormatterTest.java index 75efe5ef376..023ef4e95cf 100644 --- a/src/test/java/org/jabref/logic/layout/format/LatexToUnicodeFormatterTest.java +++ b/src/test/java/org/jabref/logic/layout/format/LatexToUnicodeFormatterTest.java @@ -156,4 +156,15 @@ public void testApostrophO() { public void testApostrophC() { assertEquals("O'Connor", formatter.format("O'Connor")); } + + @Test + public void testPreservationOfSingleUnderscore() { + assertEquals("Lorem ipsum_lorem ipsum", formatter.format("Lorem ipsum_lorem ipsum")); + } + + @Test + public void testConversionOfUnderscoreWithBraces() { + assertEquals("Lorem ipsum_(lorem ipsum)", formatter.format("Lorem ipsum_{lorem ipsum}")); + } + } diff --git a/src/test/java/org/jabref/model/entry/KeywordListTest.java b/src/test/java/org/jabref/model/entry/KeywordListTest.java index e60fb58a186..2fa3f62b2f3 100644 --- a/src/test/java/org/jabref/model/entry/KeywordListTest.java +++ b/src/test/java/org/jabref/model/entry/KeywordListTest.java @@ -72,4 +72,20 @@ public void parseWordsWithBracketsReturnsOneKeyword() throws Exception { public void asStringAddsSpaceAfterDelimiter() throws Exception { assertEquals("keywordOne, keywordTwo", keywords.getAsString(',')); } + + @Test + public void parseHierarchicalChain() throws Exception { + Keyword expected = Keyword.of("Parent", "Node", "Child"); + + assertEquals(new KeywordList(expected), KeywordList.parse("Parent > Node > Child", ',', '>')); + } + + @Test + public void parseTwoHierarchicalChains() throws Exception { + Keyword expectedOne = Keyword.of("Parent1", "Node1", "Child1"); + Keyword expectedTwo = Keyword.of("Parent2", "Node2", "Child2"); + + assertEquals(new KeywordList(expectedOne, expectedTwo), + KeywordList.parse("Parent1 > Node1 > Child1, Parent2 > Node2 > Child2", ',', '>')); + } } diff --git a/src/test/java/org/jabref/model/entry/KeywordTest.java b/src/test/java/org/jabref/model/entry/KeywordTest.java new file mode 100644 index 00000000000..43f3a642028 --- /dev/null +++ b/src/test/java/org/jabref/model/entry/KeywordTest.java @@ -0,0 +1,28 @@ +package org.jabref.model.entry; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class KeywordTest { + + @Test + public void getPathFromRootAsStringForSimpleChain() throws Exception { + Keyword keywordChain = Keyword.of("A", "B", "C"); + assertEquals("A > B", keywordChain.getChild().get().getPathFromRootAsString('>')); + } + + @Test + public void getAllSubchainsAsStringForSimpleChain() throws Exception { + Keyword keywordChain = Keyword.of("A", "B", "C"); + Set expected = new HashSet<>(); + expected.add("A"); + expected.add("A > B"); + expected.add("A > B > C"); + + assertEquals(expected, keywordChain.getAllSubchainsAsString('>')); + } +} diff --git a/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java b/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java index aa9b80c496e..8fbf4408e25 100644 --- a/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java +++ b/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java @@ -13,12 +13,12 @@ public class AutomaticKeywordGroupTest { @Test public void createSubgroupsForTwoKeywords() throws Exception { - AutomaticKeywordGroup keywordsGroup = new AutomaticKeywordGroup("Keywords", GroupHierarchyType.INDEPENDENT, "keywords", ','); + AutomaticKeywordGroup keywordsGroup = new AutomaticKeywordGroup("Keywords", GroupHierarchyType.INDEPENDENT, "keywords", ',', '>'); BibEntry entry = new BibEntry().withField("keywords", "A, B"); Set expected = new HashSet<>(); - expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("A", GroupHierarchyType.INDEPENDENT, "keywords", "A", true, ',', true))); - expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("B", GroupHierarchyType.INDEPENDENT, "keywords", "B", true, ',', true))); + expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("A", GroupHierarchyType.INCLUDING, "keywords", "A", true, ',', true))); + expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("B", GroupHierarchyType.INCLUDING, "keywords", "B", true, ',', true))); assertEquals(expected, keywordsGroup.createSubgroups(entry)); } } diff --git a/src/test/resources/testbib/complex.bib b/src/test/resources/testbib/complex.bib index e8a50a4b43f..a1e60fe364c 100644 --- a/src/test/resources/testbib/complex.bib +++ b/src/test/resources/testbib/complex.bib @@ -289,7 +289,7 @@ @Comment{jabref-meta: @Comment{jabref-meta: fileDirectory-defaultOwner-user:D:\\Documents;} -@Comment{jabref-meta: groupstree: +@Comment{jabref-meta: grouping: 0 AllEntriesGroup:; 1 StaticGroup:StaticGroup\;0\;1\;\;\;A test static group\;; 1 KeywordGroup:DynamicGroup\;0\;author\;Churchill\;0\;0\;1\;\;\;\;;