diff --git a/.circleci/config.yml b/.circleci/config.yml index d21c1c05c59..f140a5511af 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,13 +5,15 @@ jobs: docker: - image: circleci/openjdk:8-jdk steps: + - checkout + - run: git submodule sync + - run: git submodule update --init - restore_cache: keys: - - install4j - - checkout + - install4j-{{ checksum "scripts/extract-install4j.sh" }} - run: scripts/download-install4j-and-jres.sh - save_cache: - key: install4j + key: install4j-{{ checksum "scripts/extract-install4j.sh" }} paths: - "~/downloads" - "~/.install4j7" @@ -25,9 +27,11 @@ jobs: steps: - restore_cache: key: dependency-cache - - restore_cache: - key: install4j - checkout + - run: git submodule sync + - run: git submodule update --init + - restore_cache: + key: install4j-{{ checksum "scripts/extract-install4j.sh" }} - run: scripts/extract-install4j.sh - run: install4j7/bin/install4jc --verbose --license=$INSTALL4J_KEY - run: ./gradlew -Pdev=true -Pinstall4jDir="install4j7" release --stacktrace @@ -46,9 +50,11 @@ jobs: steps: - restore_cache: key: dependency-cache - - restore_cache: - key: install4j - checkout + - run: git submodule sync + - run: git submodule update --init + - restore_cache: + key: install4j-{{ checksum "scripts/extract-install4j.sh" }} - run: scripts/extract-install4j.sh - run: install4j7/bin/install4jc --verbose --license=$INSTALL4J_KEY - run: ./gradlew -Pinstall4jDir="install4j7" release --stacktrace diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..b37b24cf6d9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/main/resources/csl-styles"] + path = src/main/resources/csl-styles + url = https://github.com/citation-style-language/styles.git +[submodule "src/main/resources/csl-locales"] + path = src/main/resources/csl-locales + url = https://github.com/citation-style-language/locales.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 9848a45705f..bab3e63099a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Files without a defined external file type are now directly opened with the default application of the operating system - We streamlined the process to rename and move files by removing the confirmation dialogs. - We removed the redundant new lines of markings and wrapped the summary in the File annotation tab. [#3823](https://github.com/JabRef/jabref/issues/3823) -- We add auto url formatting when user paste link to URL field in entry editor. [#254](https://github.com/koppor/jabref/issues/254) +- We add auto url formatting when user paste link to URL field in entry editor. [koppor#254](https://github.com/koppor/jabref/issues/254) - We added a minimal height for the entry editor so that it can no longer be hidden by accident. [#4279](https://github.com/JabRef/jabref/issues/4279) - We added a new keyboard shortcut so that the entry editor could be closed by Ctrl + E. [#4222] (https://github.com/JabRef/jabref/issues/4222) - We added an option in the preference dialog box, that allows user to pick the dark or light theme option. [#4130] (https://github.com/JabRef/jabref/issues/4130) @@ -74,8 +74,8 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - We fixed an issue where files added via the "Attach file" contextmenu of an entry were not made relative. [#4201](https://github.com/JabRef/jabref/issues/4201) and [#4241](https://github.com/JabRef/jabref/issues/4241) - We fixed an issue where author list parser can't generate bibtex for Chinese author. [#4169](https://github.com/JabRef/jabref/issues/4169) - We fixed an issue where the list of XMP Exclusion fields in the preferences was not be saved [#4072](https://github.com/JabRef/jabref/issues/4072) -- We fixed an issue where the ArXiv Fetcher did not support HTTP URLs [#4367](https://github.com/JabRef/jabref/pull/4367) - +- We fixed an issue where the ArXiv Fetcher did not support HTTP URLs [koppor#328](https://github.com/koppor/jabref/issues/328) +- We fixed an issue where only one PDF file could be imported [#4422](https://github.com/JabRef/jabref/issues/4422) diff --git a/build.gradle b/build.gradle index a88052930d2..1c14214c094 100644 --- a/build.gradle +++ b/build.gradle @@ -151,9 +151,6 @@ dependencies { compile 'org.apache.logging.log4j:log4j-api:2.11.1' compile 'org.apache.logging.log4j:log4j-core:2.11.1' - // need to use snapshots as the stable version is from 2013 and doesn't support v1.0.1 CitationStyles - compile 'org.citationstyles:styles:1.0.1-SNAPSHOT' - compile 'org.citationstyles:locales:1.0.1-SNAPSHOT' compile 'de.undercouch:citeproc-java:1.0.1' compile 'com.github.tomtung:latex2unicode_2.12:0.2.2' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 20cbcee3f60..d0d037d677b 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -49,6 +49,8 @@ + + diff --git a/jabref.install4j b/jabref.install4j index a79be056141..08da26fd0a2 100644 --- a/jabref.install4j +++ b/jabref.install4j @@ -1,5 +1,5 @@ - + @@ -42,7 +42,10 @@ - + + + + @@ -105,7 +108,7 @@ - + @@ -116,6 +119,9 @@ + + + @@ -144,7 +150,7 @@ - + @@ -766,6 +772,9 @@ return console.askOkCancel(message, true); + + + @@ -1054,7 +1063,7 @@ return console.askYesNo(message, true); false - install4j + JabRef @@ -1262,7 +1271,7 @@ return console.askYesNo(message, true); - icon:${installer:sys.installerApplicationMode}_header.png + ./src/main/resources/icons/JabRef-icon-64.png diff --git a/scripts/download-install4j-and-jres.sh b/scripts/download-install4j-and-jres.sh index 0e99d438c6e..cdd5aee3b20 100755 --- a/scripts/download-install4j-and-jres.sh +++ b/scripts/download-install4j-and-jres.sh @@ -5,7 +5,7 @@ if [ ! -d ~/downloads ]; then mkdir ~/downloads fi cd ~/downloads -wget --quiet -nc http://download-keycdn.ej-technologies.com/install4j/install4j_unix_7_0_4.tar.gz +wget --quiet -nc --show-progress http://download-keycdn.ej-technologies.com/install4j/install4j_unix_7_0_8.tar.gz # fetch JREs if [ ! -d ~/.install4j7/jres ]; then diff --git a/scripts/extract-install4j.sh b/scripts/extract-install4j.sh index 4e8cfcc7dd3..9e960280b81 100755 --- a/scripts/extract-install4j.sh +++ b/scripts/extract-install4j.sh @@ -1,4 +1,4 @@ #!/bin/bash -tar -xf ~/downloads/install4j_unix_7_0_4.tar.gz +tar -xf ~/downloads/install4j_unix_7_0_8.tar.gz # fix directory name (until install4j 6.1.5 it was install4j6 -mv install4j7.0.4 install4j7 +mv install4j7.0.8 install4j7 diff --git a/src/main/java/org/jabref/JabRefGUI.java b/src/main/java/org/jabref/JabRefGUI.java index ac996a79899..57214d9ef9f 100644 --- a/src/main/java/org/jabref/JabRefGUI.java +++ b/src/main/java/org/jabref/JabRefGUI.java @@ -150,7 +150,7 @@ private void openWindow(Stage mainStage) { root.getChildren().add(JabRefGUI.mainFrame); Scene scene = new Scene(root, 800, 800); - Globals.getThemeLoader().installBaseCss(scene, Globals.prefs); + Globals.getThemeLoader().installCss(scene, Globals.prefs); mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); mainStage.setScene(scene); diff --git a/src/main/java/org/jabref/gui/Base.css b/src/main/java/org/jabref/gui/Base.css index ed4bbd29f96..349b7acc6d7 100644 --- a/src/main/java/org/jabref/gui/Base.css +++ b/src/main/java/org/jabref/gui/Base.css @@ -242,6 +242,7 @@ /* * The base css file defining the style that is valid for every pane and dialog. */ + .hyperlink { -fx-padding: 0; -fx-underline: false; @@ -630,8 +631,10 @@ } .text-input:focused { + -fx-highlight-fill: derive(-jr-accent, 20%); -fx-background-color: -jr-accent, -fx-control-inner-background; -fx-background-insets: 0, 2; + -fx-highlight-text-fill: -fx-text-inner-color; } .text-area { @@ -941,3 +944,15 @@ We want to have a look that matches our icons in the tool-bar */ -fx-text-fill: -fx-light-text-color; -fx-padding: -1ex -0.5ex -1ex -0.5ex; } + +.progress-bar > .bar { + -fx-background-color: -jr-theme; +} + +.progress-bar:indeterminate > .bar { + -fx-background-color: -jr-theme; +} + +.progress-bar > .track { + -fx-background-color: -jr-accent; +} diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 324b6805ccd..f57f69d6134 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -60,7 +60,7 @@ import org.jabref.gui.journals.UnabbreviateAction; import org.jabref.gui.maintable.MainTable; import org.jabref.gui.maintable.MainTableDataModel; -import org.jabref.gui.mergeentries.MergeEntriesDialog; +import org.jabref.gui.mergeentries.MergeEntriesAction; import org.jabref.gui.mergeentries.MergeWithFetchedEntryAction; import org.jabref.gui.specialfields.SpecialFieldDatabaseChangeListener; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; @@ -324,7 +324,7 @@ private void setupActions() { // The action for cleaning up entry. actions.put(Actions.CLEANUP, cleanUpAction); - actions.put(Actions.MERGE_ENTRIES, () -> new MergeEntriesDialog(BasePanel.this, dialogService)); + actions.put(Actions.MERGE_ENTRIES, () -> new MergeEntriesAction(frame).execute()); // The action for copying the selected entry's key. actions.put(Actions.COPY_KEY, this::copyKey); diff --git a/src/main/java/org/jabref/gui/DuplicateResolverDialog.java b/src/main/java/org/jabref/gui/DuplicateResolverDialog.java index c7d939cbd87..362333ebfa0 100644 --- a/src/main/java/org/jabref/gui/DuplicateResolverDialog.java +++ b/src/main/java/org/jabref/gui/DuplicateResolverDialog.java @@ -8,6 +8,9 @@ import javax.swing.JButton; import javax.swing.JPanel; +import javafx.scene.Scene; + +import org.jabref.gui.customjfx.CustomJFXPanel; import org.jabref.gui.help.HelpAction; import org.jabref.gui.importer.ImportInspectionDialog; import org.jabref.gui.mergeentries.MergeEntries; @@ -119,7 +122,7 @@ public void windowClosing(WindowEvent e) { } }); - getContentPane().add(me.getMergeEntryPanel()); + getContentPane().add(CustomJFXPanel.wrap(new Scene(me))); getContentPane().add(options, BorderLayout.SOUTH); pack(); diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 596fe1972c9..a7f2586d7e0 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -69,7 +69,6 @@ import org.jabref.gui.actions.ManageJournalsAction; import org.jabref.gui.actions.ManageKeywordsAction; import org.jabref.gui.actions.ManageProtectedTermsAction; -import org.jabref.gui.actions.MergeEntriesAction; import org.jabref.gui.actions.NewDatabaseAction; import org.jabref.gui.actions.NewEntryAction; import org.jabref.gui.actions.NewEntryFromPlainTextAction; @@ -97,6 +96,7 @@ import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.menus.FileHistoryMenu; +import org.jabref.gui.mergeentries.MergeEntriesAction; import org.jabref.gui.push.PushToApplicationButton; import org.jabref.gui.push.PushToApplications; import org.jabref.gui.search.GlobalSearchBar; diff --git a/src/main/java/org/jabref/gui/actions/MergeEntriesAction.java b/src/main/java/org/jabref/gui/actions/MergeEntriesAction.java deleted file mode 100644 index 257c8b04a18..00000000000 --- a/src/main/java/org/jabref/gui/actions/MergeEntriesAction.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.jabref.gui.actions; - -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.mergeentries.MergeEntriesDialog; - -public class MergeEntriesAction extends SimpleCommand { - - private final JabRefFrame jabRefFrame; - - public MergeEntriesAction(JabRefFrame jabRefFrame) { - this.jabRefFrame = jabRefFrame; - } - - @Override - public void execute() { - MergeEntriesDialog dlg = new MergeEntriesDialog(jabRefFrame.getCurrentBasePanel(), jabRefFrame.getDialogService()); - dlg.setVisible(true); - } - -} diff --git a/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java b/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java index 08a101c740d..994452e5efa 100644 --- a/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java +++ b/src/main/java/org/jabref/gui/bibtexkeypattern/BibtexKeyPatternPanel.java @@ -75,7 +75,7 @@ private void buildGUI() { rowIndex++; Label defaultPattern = new Label(Localization.lang("Default pattern")); Button button = new Button("Default"); - button.setOnAction(e-> defaultPat.setText((String) Globals.prefs.defaults.get(JabRefPreferences.DEFAULT_BIBTEX_KEY_PATTERN))); + button.setOnAction(e -> defaultPat.setText((String) Globals.prefs.defaults.get(JabRefPreferences.DEFAULT_BIBTEX_KEY_PATTERN))); gridPane.add(defaultPattern, 1, rowIndex); gridPane.add(defaultPat, 2, rowIndex); gridPane.add(button, 3, rowIndex); @@ -87,7 +87,7 @@ private void buildGUI() { Button button1 = new Button("Default"); button1.setOnAction(e1 -> textField.setText((String) Globals.prefs.defaults.get(JabRefPreferences.DEFAULT_BIBTEX_KEY_PATTERN))); - gridPane.add(label1, 1 + (columnIndex * 3) , rowIndex); + gridPane.add(label1, 1 + (columnIndex * 3), rowIndex); gridPane.add(textField, 2 + (columnIndex * 3), rowIndex); gridPane.add(button1, 3 + (columnIndex * 3), rowIndex); @@ -96,18 +96,19 @@ private void buildGUI() { if (columnIndex == COLUMNS - 1) { columnIndex = 0; rowIndex++; - } else + } else { columnIndex++; + } } rowIndex++; Button help1 = new Button("?"); - help1.setOnAction(e->new HelpAction(Localization.lang("Help on key patterns"), HelpFile.BIBTEX_KEY_PATTERN).getHelpButton().doClick()); + help1.setOnAction(e -> new HelpAction(Localization.lang("Help on key patterns"), HelpFile.BIBTEX_KEY_PATTERN).getHelpButton().doClick()); gridPane.add(help1, 1, rowIndex); Button btnDefaultAll1 = new Button(Localization.lang("Reset all")); - btnDefaultAll1.setOnAction(e-> { + btnDefaultAll1.setOnAction(e -> { // reset all fields for (TextField field : textFields.values()) { field.setText(""); diff --git a/src/main/java/org/jabref/gui/customjfx/CustomJFXPanel.java b/src/main/java/org/jabref/gui/customjfx/CustomJFXPanel.java index 5cdafa593ce..e21f8ce805e 100644 --- a/src/main/java/org/jabref/gui/customjfx/CustomJFXPanel.java +++ b/src/main/java/org/jabref/gui/customjfx/CustomJFXPanel.java @@ -13,7 +13,7 @@ public class CustomJFXPanel { public static JFXPanel wrap(Scene scene) { JFXPanel container = new JFXPanel(); - Globals.getThemeLoader().installBaseCss(scene, Globals.prefs); + Globals.getThemeLoader().installCss(scene, Globals.prefs); DefaultTaskExecutor.runInJavaFXThread(() -> container.setScene(scene)); return container; } diff --git a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java index 89fff7fa8b7..db5ce8b4b29 100644 --- a/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java +++ b/src/main/java/org/jabref/gui/edit/ReplaceStringViewModel.java @@ -14,8 +14,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; -public class ReplaceStringViewModel extends AbstractViewModel -{ +public class ReplaceStringViewModel extends AbstractViewModel { private boolean allFieldReplace; private String findString; private String replaceString; @@ -29,8 +28,7 @@ public class ReplaceStringViewModel extends AbstractViewModel private BooleanProperty selectOnlyProperty = new SimpleBooleanProperty(); - public ReplaceStringViewModel(BasePanel basePanel) - { + public ReplaceStringViewModel(BasePanel basePanel) { Objects.requireNonNull(basePanel); this.panel = basePanel; } @@ -45,20 +43,20 @@ public int replace() { final NamedCompound compound = new NamedCompound(Localization.lang("Replace string")); int counter = 0; if (selOnly) { - for (BibEntry bibEntry: this.panel.getSelectedEntries()) + for (BibEntry bibEntry : this.panel.getSelectedEntries()) { counter += replaceItem(bibEntry, compound); - } - else { - for (BibEntry bibEntry: this.panel.getDatabase().getEntries()) + } + } else { + for (BibEntry bibEntry : this.panel.getDatabase().getEntries()) { counter += replaceItem(bibEntry, compound); + } } return counter; } /** - * Does the actual operation on a Bibtex entry based on the - * settings specified in this same dialog. Returns the number of - * occurrences replaced. + * Does the actual operation on a Bibtex entry based on the settings specified in this same dialog. Returns the + * number of occurrences replaced. */ private int replaceItem(BibEntry entry, NamedCompound compound) { int counter = 0; diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeView.java b/src/main/java/org/jabref/gui/groups/GroupTreeView.java index 4e4aee8051e..6cdcae3c8ee 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeView.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeView.java @@ -195,20 +195,21 @@ public void initialize() { // Drag and drop support row.setOnDragDetected(event -> { - TreeItem selectedItem = treeTable.getSelectionModel().getSelectedItem(); - if ((selectedItem != null) && (selectedItem.getValue() != null)) { - Dragboard dragboard = treeTable.startDragAndDrop(TransferMode.MOVE); - - // Display the group when dragging - dragboard.setDragView(row.snapshot(null, null)); - - // Put the group node as content - ClipboardContent content = new ClipboardContent(); - content.put(DragAndDropDataFormats.GROUP, selectedItem.getValue().getPath()); - dragboard.setContent(content); - - event.consume(); + List groupsToMove = new ArrayList<>(); + for (TreeItem selectedItem : treeTable.getSelectionModel().getSelectedItems()) { + if ((selectedItem != null) && (selectedItem.getValue() != null)) { + groupsToMove.add(selectedItem.getValue().getPath()); + } } + + // Put the group nodes as content + Dragboard dragboard = treeTable.startDragAndDrop(TransferMode.MOVE); + // Display the group when dragging + dragboard.setDragView(row.snapshot(null, null)); + ClipboardContent content = new ClipboardContent(); + content.put(DragAndDropDataFormats.GROUP, groupsToMove); + dragboard.setContent(content); + event.consume(); }); row.setOnDragOver(event -> { Dragboard dragboard = event.getDragboard(); @@ -240,13 +241,16 @@ public void initialize() { row.setOnDragDropped(event -> { Dragboard dragboard = event.getDragboard(); boolean success = false; + if (dragboard.hasContent(DragAndDropDataFormats.GROUP)) { - String pathToSource = (String) dragboard.getContent(DragAndDropDataFormats.GROUP); - Optional source = viewModel.rootGroupProperty().get() - .getChildByPath(pathToSource); - if (source.isPresent()) { - source.get().draggedOn(row.getItem(), getDroppingMouseLocation(row, event)); - success = true; + List pathToSources = (List) dragboard.getContent(DragAndDropDataFormats.GROUP); + for (String pathToSource : pathToSources) { + Optional source = viewModel.rootGroupProperty().get() + .getChildByPath(pathToSource); + if (source.isPresent()) { + source.get().draggedOn(row.getItem(), getDroppingMouseLocation(row, event)); + success = true; + } } } diff --git a/src/main/java/org/jabref/gui/mergeentries/DiffHighlighting.java b/src/main/java/org/jabref/gui/mergeentries/DiffHighlighting.java new file mode 100644 index 00000000000..f4812fbc4ad --- /dev/null +++ b/src/main/java/org/jabref/gui/mergeentries/DiffHighlighting.java @@ -0,0 +1,108 @@ +package org.jabref.gui.mergeentries; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.scene.text.Text; + +import difflib.Delta; +import difflib.DiffUtils; + +public class DiffHighlighting { + + private DiffHighlighting() { + } + + public static List generateDiffHighlighting(String baseString, String modifiedString, String separator) { + List stringList = Arrays.asList(baseString.split(separator)); + List result = stringList.stream().map(DiffHighlighting::forUnchanged).collect(Collectors.toList()); + List> deltaList = DiffUtils.diff(stringList, Arrays.asList(modifiedString.split(separator))).getDeltas(); + Collections.reverse(deltaList); + for (Delta delta : deltaList) { + int startPos = delta.getOriginal().getPosition(); + List lines = delta.getOriginal().getLines(); + int offset = 0; + switch (delta.getType()) { + case CHANGE: + for (String line : lines) { + result.set(startPos + offset, forRemoved(line + separator)); + offset++; + } + result.set(startPos + offset - 1, forRemoved(stringList.get((startPos + offset) - 1) + separator)); + result.add(startPos + offset, forAdded(String.join(separator, delta.getRevised().getLines()))); + break; + case DELETE: + for (String line : lines) { + result.set(startPos + offset, forRemoved(line + separator)); + offset++; + } + break; + case INSERT: + result.add(delta.getOriginal().getPosition(), forAdded(String.join(separator, delta.getRevised().getLines()))); + break; + default: + break; + } + } + return result; + } + + public static Text forChanged(String text) { + Text node = new Text(text); + node.getStyleClass().add("text-changed"); + return node; + } + + public static Text forUnchanged(String text) { + Text node = new Text(text); + node.getStyleClass().add("text-unchanged"); + return node; + } + + public static Text forAdded(String text) { + Text node = new Text(text); + node.getStyleClass().add("text-added"); + return node; + } + + public static Text forRemoved(String text) { + Text node = new Text(text); + node.getStyleClass().add("text-removed"); + return node; + } + + public static List generateSymmetricHighlighting(String baseString, String modifiedString, String separator) { + List stringList = Arrays.asList(baseString.split(separator)); + List result = stringList.stream().map(text -> DiffHighlighting.forUnchanged(text + separator)).collect(Collectors.toList()); + List> deltaList = DiffUtils.diff(stringList, Arrays.asList(modifiedString.split(separator))).getDeltas(); + Collections.reverse(deltaList); + for (Delta delta : deltaList) { + int startPos = delta.getOriginal().getPosition(); + List lines = delta.getOriginal().getLines(); + int offset = 0; + switch (delta.getType()) { + case CHANGE: + for (String line : lines) { + result.set(startPos + offset, forChanged(line + separator)); + offset++; + } + break; + case DELETE: + for (String line : lines) { + result.set(startPos + offset, forAdded(line + separator)); + offset++; + } + break; + case INSERT: + break; + default: + break; + } + } + + return result; + } + +} diff --git a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java index 38a332e6dd3..cd7be54acc3 100644 --- a/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java +++ b/src/main/java/org/jabref/gui/mergeentries/FetchAndMergeEntry.java @@ -4,18 +4,25 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; import org.jabref.Globals; import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableChangeType; +import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.EntryBasedFetcher; import org.jabref.logic.importer.IdBasedFetcher; +import org.jabref.logic.importer.WebFetcher; import org.jabref.logic.importer.WebFetchers; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; +import org.jabref.model.entry.InternalBibtexFields; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,8 +63,7 @@ public void fetchAndMerge(BibEntry entry, List fields) { .onSuccess(fetchedEntry -> { String type = FieldName.getDisplayName(field); if (fetchedEntry.isPresent()) { - MergeFetchedEntryDialog dialog = new MergeFetchedEntryDialog(panel, entry, fetchedEntry.get(), type); - dialog.setVisible(true); + showMergeDialog(entry, fetchedEntry.get(), fetcher.get()); } else { panel.frame().setStatus(Localization.lang("Cannot get info based on given %0: %1", type, fieldContent.get())); } @@ -74,12 +80,69 @@ public void fetchAndMerge(BibEntry entry, List fields) { } } + private void showMergeDialog(BibEntry originalEntry, BibEntry fetchedEntry, WebFetcher fetcher) { + MergeEntriesDialog dialog = new MergeEntriesDialog(originalEntry, fetchedEntry, panel.getBibDatabaseContext().getMode()); + dialog.setTitle(Localization.lang("Merge entry with %0 information", fetcher.getName())); + dialog.setLeftHeaderText(Localization.lang("Original entry")); + dialog.setRightHeaderText(Localization.lang("Entry from %0", fetcher.getName())); + Optional mergedEntry = dialog.showAndWait(); + if (mergedEntry.isPresent()) { + NamedCompound ce = new NamedCompound(Localization.lang("Merge entry with %0 information", fetcher.getName())); + + // Updated the original entry with the new fields + Set jointFields = new TreeSet<>(mergedEntry.get().getFieldNames()); + Set originalFields = new TreeSet<>(originalEntry.getFieldNames()); + boolean edited = false; + + // entry type + String oldType = originalEntry.getType(); + String newType = mergedEntry.get().getType(); + + if (!oldType.equalsIgnoreCase(newType)) { + originalEntry.setType(newType); + ce.addEdit(new UndoableChangeType(originalEntry, oldType, newType)); + edited = true; + } + + // fields + for (String field : jointFields) { + Optional originalString = originalEntry.getField(field); + Optional mergedString = mergedEntry.get().getField(field); + if (!originalString.isPresent() || !originalString.equals(mergedString)) { + originalEntry.setField(field, mergedString.get()); // mergedString always present + ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.orElse(null), + mergedString.get())); + edited = true; + } + } + + // Remove fields which are not in the merged entry, unless they are internal fields + for (String field : originalFields) { + if (!jointFields.contains(field) && !InternalBibtexFields.isInternalField(field)) { + Optional originalString = originalEntry.getField(field); + originalEntry.clearField(field); + ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.get(), null)); // originalString always present + edited = true; + } + } + + if (edited) { + ce.end(); + panel.getUndoManager().addEdit(ce); + dialogService.notify(Localization.lang("Updated entry with info from %0", fetcher.getName())); + } else { + dialogService.notify(Localization.lang("No information added")); + } + } else { + dialogService.notify(Localization.lang("Canceled merging entries")); + } + } + public void fetchAndMerge(BibEntry entry, EntryBasedFetcher fetcher) { BackgroundTask.wrap(() -> fetcher.performSearch(entry).stream().findFirst()) .onSuccess(fetchedEntry -> { if (fetchedEntry.isPresent()) { - MergeFetchedEntryDialog dialog = new MergeFetchedEntryDialog(panel, entry, fetchedEntry.get(), fetcher.getName()); - dialog.setVisible(true); + showMergeDialog(entry, fetchedEntry.get(), fetcher); } else { dialogService.notify(Localization.lang("Could not find any bibliographic information.")); } diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntries.css b/src/main/java/org/jabref/gui/mergeentries/MergeEntries.css new file mode 100644 index 00000000000..9c110d24857 --- /dev/null +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntries.css @@ -0,0 +1,15 @@ +.text-changed { + -fx-fill: darkgreen; +} + +.text-unchanged { + +} + +.text-added { + -fx-fill: blue; +} + +.text-removed { + -fx-fill: red; +} diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java index 1e4db5bcc0f..9deceb0218b 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java @@ -1,11 +1,9 @@ package org.jabref.gui.mergeentries; -import java.awt.Font; -import java.io.IOException; -import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -13,62 +11,38 @@ import java.util.Optional; import java.util.Set; import java.util.TreeSet; - -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JTextArea; -import javax.swing.JTextPane; -import javax.swing.ScrollPaneConstants; -import javax.swing.SwingUtilities; - -import javafx.embed.swing.JFXPanel; -import javafx.scene.Scene; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.geometry.Insets; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; import org.jabref.Globals; -import org.jabref.gui.FXDialogService; -import org.jabref.gui.PreviewPanel; -import org.jabref.gui.customjfx.CustomJFXPanel; -import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.gui.util.component.DiffHighlightingTextPane; -import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.LatexFieldFormatter; import org.jabref.logic.formatter.casechanger.SentenceCaseFormatter; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.strings.DiffHighlighting; -import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.InternalBibtexFields; import org.jabref.preferences.JabRefPreferences; -import com.jgoodies.forms.layout.CellConstraints; -import com.jgoodies.forms.layout.ColumnSpec; -import com.jgoodies.forms.layout.FormLayout; -import com.jgoodies.forms.layout.RowSpec; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MergeEntries { - - private static final Logger LOGGER = LoggerFactory.getLogger(MergeEntries.class); - - - private static final String MARGIN = "10px"; +import org.fxmisc.easybind.EasyBind; - private static final List HEADING_LABELS = new ArrayList<>(6); +public class MergeEntries extends BorderPane { - private static final CellConstraints CELL_CONSTRAINTS = new CellConstraints(); - private static final String[] DIFF_MODES = {Localization.lang("Plain text"), - Localization.lang("Show diff") + " - " + Localization.lang("word"), - Localization.lang("Show diff") + " - " + Localization.lang("character"), - Localization.lang("Show symmetric diff") + " - " + Localization.lang("word"), - Localization.lang("Show symmetric diff") + " - " + Localization.lang("character")}; + private final ComboBox diffMode = new ComboBox<>(); // Headings private final List columnHeadings = Arrays.asList(Localization.lang("Field"), @@ -82,22 +56,30 @@ public class MergeEntries { private final BibEntry mergedEntry = new BibEntry(); private final BibEntry leftEntry; private final BibEntry rightEntry; - private final BibDatabaseMode databaseType; - private JScrollPane scrollPane; - private JTextArea sourceView; - private PreviewPanel entryPreview; - private Boolean doneBuilding; - private Boolean identicalTypes; - private List typeRadioButtons; + private final Map leftTextPanes = new HashMap<>(); private final Set allFields = new TreeSet<>(); - private final JComboBox diffMode = new JComboBox<>(); - private final Map leftTextPanes = new HashMap<>(); - private final Map rightTextPanes = new HashMap<>(); - - private final Map> radioButtons = new HashMap<>(); + private final Map rightTextPanes = new HashMap<>(); + private final Map> radioButtons = new HashMap<>(); + private Boolean identicalTypes; + private List typeRadioButtons; - private final JPanel mainPanel = new JPanel(); + /** + * Constructor with optional column captions for the two entries + * + * @param entryLeft Left entry + * @param entryRight Right entry + * @param headingLeft Heading for left entry + * @param headingRight Heading for right entry + * @param type Bib database mode + */ + public MergeEntries(BibEntry entryLeft, BibEntry entryRight, String headingLeft, String headingRight, BibDatabaseMode type) { + this.leftEntry = entryLeft; + this.rightEntry = entryRight; + initialize(); + setLeftHeaderText(headingLeft); + setRightHeaderText(headingRight); + } /** @@ -110,112 +92,69 @@ public class MergeEntries { public MergeEntries(BibEntry entryLeft, BibEntry entryRight, BibDatabaseMode type) { leftEntry = entryLeft; rightEntry = entryRight; - this.databaseType = type; initialize(); } - /** - * Constructor with optional column captions for the two entries - * - * @param entryLeft Left entry - * @param entryRight Right entry - * @param headingLeft Heading for left entry - * @param headingRight Heading for right entry - * @param type Bib database mode - */ - public MergeEntries(BibEntry entryLeft, BibEntry entryRight, String headingLeft, String headingRight, BibDatabaseMode type) { - columnHeadings.set(1, headingLeft); - columnHeadings.set(5, headingRight); - this.leftEntry = entryLeft; - this.rightEntry = entryRight; - - this.databaseType = type; - - initialize(); + private static String getDisplayText(DiffMode mode) { + switch (mode) { + case PLAIN: + return Localization.lang("Plain text"); + case WORD: + return Localization.lang("Show diff") + " - " + Localization.lang("word"); + case CHARACTER: + return Localization.lang("Show diff") + " - " + Localization.lang("character"); + case WORD_SYMMETRIC: + return Localization.lang("Show symmetric diff") + " - " + Localization.lang("word"); + case CHARACTER_SYMMETRIC: + return Localization.lang("Show symmetric diff") + " - " + Localization.lang("character"); + default: + throw new UnsupportedOperationException("Not implemented: " + mode); + } } /** * Main function for building the merge entry JPanel */ private void initialize() { - doneBuilding = false; + setPrefWidth(800); + setupFields(); fillDiffModes(); - // Create main layout - String colSpecMain = "left:pref, 5px, center:3cm:grow, 5px, center:pref, 3px, center:pref, 3px, center:pref, 5px, center:3cm:grow"; - String colSpecMerge = "left:pref, 5px, fill:3cm:grow, 5px, center:pref, 3px, center:pref, 3px, center:pref, 5px, fill:3cm:grow"; - String rowSpec = "pref, pref, 10px, fill:5cm:grow, 10px, pref, 10px, fill:3cm:grow"; - StringBuilder rowBuilder = new StringBuilder(""); - for (int i = 0; i < allFields.size(); i++) { - rowBuilder.append("pref, 2dlu, "); - } - rowBuilder.append("pref"); - - JPanel mergePanel = new JPanel(); - FormLayout mainLayout = new FormLayout(colSpecMain, rowSpec); - FormLayout mergeLayout = new FormLayout(colSpecMerge, rowBuilder.toString()); - mainPanel.setLayout(mainLayout); - mergePanel.setLayout(mergeLayout); - - setupHeadingRows(); - - mainPanel.add(new JSeparator(), CELL_CONSTRAINTS.xyw(1, 3, 11)); - + GridPane mergePanel = new GridPane(); + mergePanel.setVgap(10); + mergePanel.setHgap(15); + ColumnConstraints columnLabel = new ColumnConstraints(); + columnLabel.setHgrow(Priority.NEVER); + ColumnConstraints columnValues = new ColumnConstraints(); + columnValues.setHgrow(Priority.ALWAYS); + columnValues.setPercentWidth(40); + ColumnConstraints columnSelect = new ColumnConstraints(); + columnSelect.setHgrow(Priority.NEVER); + mergePanel.getColumnConstraints().setAll(columnLabel, columnValues, columnSelect, columnSelect, columnSelect, columnValues); + + setupHeadingRows(mergePanel); setupEntryTypeRow(mergePanel); + setupFieldRows(mergePanel); - int maxLabelWidth = setupFieldRows(mergePanel); - - // Create and add scrollpane - scrollPane = new JScrollPane(mergePanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.setBorder(BorderFactory.createEmptyBorder()); - updateTextPanes(allFields); - mainPanel.add(scrollPane, CELL_CONSTRAINTS.xyw(1, 4, 11)); - mainPanel.add(new JSeparator(), CELL_CONSTRAINTS.xyw(1, 5, 11)); - - synchronizeColumnWidths(mainLayout, mergeLayout, maxLabelWidth); - - // Setup a PreviewPanel and a Bibtex source box for the merged entry - mainPanel.add(boldFontLabel(Localization.lang("Merged entry")), CELL_CONSTRAINTS.xyw(1, 6, 6)); - - entryPreview = new PreviewPanel(null, new BibDatabaseContext(), Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService(), ExternalFileTypes.getInstance()); - entryPreview.setEntry(mergedEntry); - JFXPanel container = CustomJFXPanel.wrap(new Scene(entryPreview)); - mainPanel.add(container, CELL_CONSTRAINTS.xyw(1, 8, 6)); - - mainPanel.add(boldFontLabel(Localization.lang("Merged BibTeX source code")), CELL_CONSTRAINTS.xyw(8, 6, 4)); - - sourceView = new JTextArea(); - sourceView.setLineWrap(true); - sourceView.setFont(new Font("Monospaced", Font.PLAIN, Globals.prefs.getInt(JabRefPreferences.FONT_SIZE))); - mainPanel.add(new JScrollPane(sourceView), CELL_CONSTRAINTS.xyw(8, 8, 4)); - sourceView.setEditable(false); - - // Add some margin around the layout - mainLayout.appendRow(RowSpec.decode(MARGIN)); - mainLayout.appendColumn(ColumnSpec.decode(MARGIN)); - mainLayout.insertRow(1, RowSpec.decode(MARGIN)); - mainLayout.insertColumn(1, ColumnSpec.decode(MARGIN)); + ScrollPane scrollPane = new ScrollPane(mergePanel); + scrollPane.setFitToWidth(true); + setCenter(scrollPane); - // Everything done, allow any action to actually update the merged entry - doneBuilding = true; + updateFieldValues(allFields); - updateAll(); + updateMergedEntry(); - // Show what we've got - mainPanel.setVisible(true); - SwingUtilities.invokeLater(() -> scrollPane.getVerticalScrollBar().setValue(0)); + getStylesheets().add(0, MergeEntries.class.getResource("MergeEntries.css").toExternalForm()); } - private int setupFieldRows(JPanel mergePanel) { + private void setupFieldRows(GridPane mergePanel) { // For all fields in joint add a row and possibly radio buttons int row = 2; - int maxLabelWidth = -1; for (String field : allFields) { - JLabel label = boldFontLabel(new SentenceCaseFormatter().format(field)); - mergePanel.add(label, CELL_CONSTRAINTS.xy(1, (2 * row) - 1, "left, top")); + Label label = new Label(new SentenceCaseFormatter().format(field)); + mergePanel.add(label, 0, row); Optional leftString = leftEntry.getField(field); Optional rightString = rightEntry.getField(field); if (leftString.equals(rightString)) { @@ -224,12 +163,10 @@ private int setupFieldRows(JPanel mergePanel) { differentFields.add(field); } - maxLabelWidth = Math.max(maxLabelWidth, label.getPreferredSize().width); - // Left text pane if (leftString.isPresent()) { - JTextPane tf = new DiffHighlightingTextPane(); - mergePanel.add(tf, CELL_CONSTRAINTS.xy(3, (2 * row) - 1, "f, f")); + TextFlow tf = new DiffHighlightingTextPane(); + mergePanel.add(tf, 1, row); leftTextPanes.put(field, tf); } @@ -237,167 +174,133 @@ private int setupFieldRows(JPanel mergePanel) { if (identicalFields.contains(field)) { mergedEntry.setField(field, leftString.get()); // Will only happen if both entries have the field and the content is identical } else { - ButtonGroup group = new ButtonGroup(); - List list = new ArrayList<>(3); + ToggleGroup group = new ToggleGroup(); + List list = new ArrayList<>(3); for (int k = 0; k < 3; k++) { - JRadioButton button = new JRadioButton(); - group.add(button); - mergePanel.add(button, CELL_CONSTRAINTS.xy(5 + (k * 2), (2 * row) - 1)); - button.addChangeListener(e -> updateAll()); + RadioButton button = new RadioButton(); + EasyBind.subscribe(button.selectedProperty(), selected -> updateMergedEntry()); + group.getToggles().add(button); + mergePanel.add(button, 2 + k, row); list.add(button); } radioButtons.put(field, list); if (leftString.isPresent()) { list.get(0).setSelected(true); if (!rightString.isPresent()) { - list.get(2).setEnabled(false); + list.get(2).setDisable(true); } } else { - list.get(0).setEnabled(false); + list.get(0).setDisable(true); list.get(2).setSelected(true); } } // Right text pane if (rightString.isPresent()) { - JTextPane tf = new DiffHighlightingTextPane(); - mergePanel.add(tf, CELL_CONSTRAINTS.xy(11, (2 * row) - 1, "f, f")); + TextFlow tf = new DiffHighlightingTextPane(); + mergePanel.add(tf, 5, row); rightTextPanes.put(field, tf); } row++; } - return maxLabelWidth; } - private void setupEntryTypeRow(JPanel mergePanel) { + private void setupEntryTypeRow(GridPane mergePanel) { // Start with entry type - mergePanel.add(boldFontLabel(Localization.lang("Entry type")), CELL_CONSTRAINTS.xy(1, 1)); + mergePanel.add(new Label(Localization.lang("Entry type")), 0, 1); - JTextPane leftTypeDisplay = new DiffHighlightingTextPane(); - leftTypeDisplay.setText(DiffHighlighting.HTML_START + leftEntry.getType() + DiffHighlighting.HTML_END); - mergePanel.add(leftTypeDisplay, CELL_CONSTRAINTS.xy(3, 1)); if (leftEntry.getType().equals(rightEntry.getType())) { + mergePanel.add(DiffHighlighting.forUnchanged(leftEntry.getType()), 1, 1); + mergePanel.add(DiffHighlighting.forUnchanged(rightEntry.getType()), 5, 1); identicalTypes = true; } else { + mergePanel.add(DiffHighlighting.forChanged(leftEntry.getType()), 1, 1); + mergePanel.add(DiffHighlighting.forChanged(rightEntry.getType()), 5, 1); identicalTypes = false; - ButtonGroup group = new ButtonGroup(); + ToggleGroup group = new ToggleGroup(); typeRadioButtons = new ArrayList<>(2); for (int k = 0; k < 3; k += 2) { - JRadioButton button = new JRadioButton(); + RadioButton button = new RadioButton(); + EasyBind.subscribe(button.selectedProperty(), selected -> updateMergedEntry()); typeRadioButtons.add(button); - group.add(button); - mergePanel.add(button, CELL_CONSTRAINTS.xy(5 + (k * 2), 1)); - button.addChangeListener(e -> updateAll()); + group.getToggles().add(button); + mergePanel.add(button, 2 + k, 1); } typeRadioButtons.get(0).setSelected(true); } - JTextPane rightTypeDisplay = new DiffHighlightingTextPane(); - rightTypeDisplay.setText(DiffHighlighting.HTML_START + rightEntry.getType() + DiffHighlighting.HTML_END); - mergePanel.add(rightTypeDisplay, CELL_CONSTRAINTS.xy(11, 1)); } - private void setupHeadingRows() { - mainPanel.add(boldFontLabel(Localization.lang("Use")), CELL_CONSTRAINTS.xyw(4, 1, 7, "center, bottom")); - mainPanel.add(diffMode, CELL_CONSTRAINTS.xy(11, 1, "right, bottom")); - + private void setupHeadingRows(GridPane mergePanel) { // Set headings for (int i = 0; i < 6; i++) { - HEADING_LABELS.add(boldFontLabel(columnHeadings.get(i))); - mainPanel.add(HEADING_LABELS.get(i), CELL_CONSTRAINTS.xy(1 + (i * 2), 2)); + mergePanel.add(new Label(columnHeadings.get(i)), i, 0); } } private void fillDiffModes() { - // Fill diff mode combo box - for (String diffText : DIFF_MODES) { - diffMode.addItem(diffText); - } - diffMode.setSelectedIndex( - Math.min(Globals.prefs.getInt(JabRefPreferences.MERGE_ENTRIES_DIFF_MODE), diffMode.getItemCount() - 1)); - diffMode.addActionListener(e -> { - updateTextPanes(differentFields); - storePreference(); + diffMode.setItems(FXCollections.observableList(Arrays.asList(DiffMode.values()))); + new ViewModelListCellFactory() + .withText(MergeEntries::getDisplayText) + .install(diffMode); + DiffMode diffModePref = Globals.prefs.getAsOptional(JabRefPreferences.MERGE_ENTRIES_DIFF_MODE) + .flatMap(DiffMode::parse) + .orElse(DiffMode.WORD); + diffMode.setValue(diffModePref); + EasyBind.subscribe(this.diffMode.valueProperty(), mode -> { + updateFieldValues(differentFields); + Globals.prefs.put(JabRefPreferences.MERGE_ENTRIES_DIFF_MODE, mode.name()); }); - } - - private void synchronizeColumnWidths(FormLayout mainLayout, FormLayout mergeLayout, - int maxLabelWidth) { - // Synchronize column widths - String[] rbAlign = {"right", "center", "left"}; - mainLayout.setColumnSpec(1, ColumnSpec.decode(Integer.toString(maxLabelWidth) + "px")); - Integer maxRBWidth = -1; - for (int k = 2; k < 5; k++) { - maxRBWidth = Math.max(maxRBWidth, HEADING_LABELS.get(k).getPreferredSize().width); - } - for (int k = 0; k < 3; k++) { - mergeLayout.setColumnSpec(5 + (k * 2), ColumnSpec.decode(rbAlign[k] + ":" + maxRBWidth + "px")); - } - } - - private JLabel boldFontLabel(String text) { - JLabel label = new JLabel(text); - Font font = label.getFont(); - label.setFont(font.deriveFont(font.getStyle() | Font.BOLD)); - return label; - } - private void storePreference() { - Globals.prefs.putInt(JabRefPreferences.MERGE_ENTRIES_DIFF_MODE, diffMode.getSelectedIndex()); + HBox heading = new HBox(10); + heading.getChildren().setAll(this.diffMode); + setTop(heading); + BorderPane.setMargin(heading, new Insets(0, 0, 10, 0)); } private void setupFields() { allFields.addAll(leftEntry.getFieldNames()); allFields.addAll(rightEntry.getFieldNames()); - // Remove internal fields - Set toberemoved = new TreeSet<>(); - for (String field : allFields) { - if (InternalBibtexFields.isInternalField(field)) { - toberemoved.add(field); - } - } - allFields.removeAll(toberemoved); + // Do not show internal fields + Set internalFields = allFields.stream().filter(InternalBibtexFields::isInternalField).collect(Collectors.toSet()); + allFields.removeAll(internalFields); } - private void updateTextPanes(Collection fields) { - int oldScrollPaneValue = scrollPane.getVerticalScrollBar().getValue(); + private void updateFieldValues(Collection fields) { for (String field : fields) { String leftString = leftEntry.getField(field).orElse(""); String rightString = rightEntry.getField(field).orElse(""); - switch (diffMode.getSelectedIndex()) { - case 0: // Plain text - break; - case 1: // Latexdiff style - word - rightString = DiffHighlighting.generateDiffHighlighting(leftString, rightString, " "); - break; - case 2: // Latexdiff style - character - rightString = DiffHighlighting.generateDiffHighlighting(leftString, rightString, ""); - break; - case 3: // Symmetric style - word - String tmpLeftString = DiffHighlighting.generateSymmetricHighlighting(leftString, rightString, " "); - rightString = DiffHighlighting.generateSymmetricHighlighting(rightString, leftString, " "); - leftString = tmpLeftString; - break; - case 4: // Symmetric style - character - tmpLeftString = DiffHighlighting.generateSymmetricHighlighting(leftString, rightString, ""); - rightString = DiffHighlighting.generateSymmetricHighlighting(rightString, leftString, ""); - leftString = tmpLeftString; - break; - default: // Shouldn't happen - break; + List leftText = leftString.isEmpty() ? Collections.emptyList() : Collections.singletonList(DiffHighlighting.forUnchanged(leftString)); + List rightText = rightString.isEmpty() ? Collections.emptyList() : Collections.singletonList(DiffHighlighting.forUnchanged(rightString)); + switch (diffMode.getValue()) { + case PLAIN: + break; + case WORD: + rightText = DiffHighlighting.generateDiffHighlighting(leftString, rightString, " "); + break; + case CHARACTER: + rightText = DiffHighlighting.generateDiffHighlighting(leftString, rightString, ""); + break; + case WORD_SYMMETRIC: + leftText = DiffHighlighting.generateSymmetricHighlighting(leftString, rightString, " "); + rightText = DiffHighlighting.generateSymmetricHighlighting(rightString, leftString, " "); + break; + case CHARACTER_SYMMETRIC: + leftText = DiffHighlighting.generateSymmetricHighlighting(leftString, rightString, ""); + rightText = DiffHighlighting.generateSymmetricHighlighting(rightString, leftString, ""); + break; + default: + throw new UnsupportedOperationException("Not implemented " + diffMode.getValue()); } - if ((leftString != null) && leftTextPanes.containsKey(field)) { - leftTextPanes.get(field).setText(DiffHighlighting.HTML_START + leftString + DiffHighlighting.HTML_END); + if (!leftText.isEmpty() && leftTextPanes.containsKey(field)) { + leftTextPanes.get(field).getChildren().setAll(leftText); } - if ((rightString != null) && rightTextPanes.containsKey(field)) { - rightTextPanes.get(field).setText(DiffHighlighting.HTML_START + rightString + DiffHighlighting.HTML_END); + if (!rightText.isEmpty() && rightTextPanes.containsKey(field)) { + rightTextPanes.get(field).getChildren().setAll(rightText); } } - SwingUtilities.invokeLater(() -> scrollPane.getVerticalScrollBar() - .setValue(Math.min(scrollPane.getVerticalScrollBar().getMaximum(), oldScrollPaneValue))); } - /** * @return Merged BibEntry */ @@ -406,22 +309,11 @@ public BibEntry getMergeEntry() { } /** - * @return The merge entry JPanel - */ - public JPanel getMergeEntryPanel() { - return mainPanel; - } - - /** - * Update the merged BibEntry with source and preview panel every time something is changed + * Update the merged entry */ - private void updateAll() { - if (!doneBuilding) { - // If we are not done adding everything, do not do anything... - return; - } + private void updateMergedEntry() { // Check if the type has changed - if (!identicalTypes && typeRadioButtons.get(0).isSelected()) { + if (!identicalTypes && !typeRadioButtons.isEmpty() && typeRadioButtons.get(0).isSelected()) { mergedEntry.setType(leftEntry.getType()); } else { mergedEntry.setType(rightEntry.getType()); @@ -429,6 +321,10 @@ private void updateAll() { // Check the potentially different fields for (String field : differentFields) { + if (!radioButtons.containsKey(field)) { + // May happen during initialization -> just ignore + continue; + } if (radioButtons.get(field).get(0).isSelected()) { mergedEntry.setField(field, leftEntry.getField(field).get()); // Will only happen if field exists } else if (radioButtons.get(field).get(2).isSelected()) { @@ -437,19 +333,31 @@ private void updateAll() { mergedEntry.clearField(field); } } + } - // Update the PreviewPanel - entryPreview.setEntry(mergedEntry); + public void setLeftHeaderText(String leftHeaderText) { + columnHeadings.set(1, leftHeaderText); + initialize(); + } - // Update the BibTeX source view - StringWriter writer = new StringWriter(); - try { - new BibEntryWriter(new LatexFieldFormatter(Globals.prefs.getLatexFieldFormatterPreferences()), - false).write(mergedEntry, writer, databaseType); - } catch (IOException ex) { - LOGGER.error("Error in entry", ex); + public void setRightHeaderText(String rightHeaderText) { + columnHeadings.set(5, rightHeaderText); + initialize(); + } + + public enum DiffMode { + PLAIN, + WORD, + CHARACTER, + WORD_SYMMETRIC, + CHARACTER_SYMMETRIC; + + public static Optional parse(String name) { + try { + return Optional.of(DiffMode.valueOf(name)); + } catch (IllegalArgumentException e) { + return Optional.empty(); + } } - sourceView.setText(writer.getBuffer().toString()); - sourceView.setCaretPosition(0); } } diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java new file mode 100644 index 00000000000..22ba15d7d8a --- /dev/null +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesAction.java @@ -0,0 +1,68 @@ +package org.jabref.gui.mergeentries; + +import java.util.List; +import java.util.Optional; + +import org.jabref.gui.BasePanel; +import org.jabref.gui.DialogService; +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableInsertEntry; +import org.jabref.gui.undo.UndoableRemoveEntry; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; + +public class MergeEntriesAction extends SimpleCommand { + + private final JabRefFrame jabRefFrame; + private final DialogService dialogService; + + public MergeEntriesAction(JabRefFrame jabRefFrame) { + this.jabRefFrame = jabRefFrame; + dialogService = jabRefFrame.getDialogService(); + } + + @Override + public void execute() { + BasePanel basePanel = jabRefFrame.getCurrentBasePanel(); + + // Check if there are two entries selected + List selectedEntries = basePanel.getSelectedEntries(); + if (selectedEntries.size() != 2) { + // Inform the user to select entries first. + dialogService.showInformationDialogAndWait( + Localization.lang("Merge entries"), + Localization.lang("You have to choose exactly two entries to merge.")); + + return; + } + + // Store the two entries + BibEntry one = selectedEntries.get(0); + BibEntry two = selectedEntries.get(1); + + MergeEntriesDialog dlg = new MergeEntriesDialog(one, two, basePanel.getBibDatabaseContext().getMode()); + dlg.setTitle(Localization.lang("Merge entries")); + Optional mergedEntry = dlg.showAndWait(); + if (mergedEntry.isPresent()) { + basePanel.insertEntry(mergedEntry.get()); + + // Create a new entry and add it to the undo stack + // Remove the other two entries and add them to the undo stack (which is not working...) + NamedCompound ce = new NamedCompound(Localization.lang("Merge entries")); + ce.addEdit(new UndoableInsertEntry(basePanel.getDatabase(), mergedEntry.get())); + ce.addEdit(new UndoableRemoveEntry(basePanel.getDatabase(), one, basePanel)); + basePanel.getDatabase().removeEntry(one); + ce.addEdit(new UndoableRemoveEntry(basePanel.getDatabase(), two, basePanel)); + basePanel.getDatabase().removeEntry(two); + ce.end(); + basePanel.getUndoManager().addEdit(ce); + + dialogService.notify(Localization.lang("Merged entries")); + } else { + dialogService.notify(Localization.lang("Canceled merging entries")); + } + } + +} diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java index f081759bb67..251f62cfdc6 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntriesDialog.java @@ -1,124 +1,47 @@ package org.jabref.gui.mergeentries; -import java.util.List; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; -import javax.swing.JButton; -import javax.swing.JSeparator; - -import org.jabref.gui.BasePanel; -import org.jabref.gui.DialogService; -import org.jabref.gui.JabRefDialog; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.gui.undo.UndoableInsertEntry; -import org.jabref.gui.undo.UndoableRemoveEntry; -import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.WindowLocation; +import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.layout.CellConstraints; -import com.jgoodies.forms.layout.ColumnSpec; -import com.jgoodies.forms.layout.FormLayout; -import com.jgoodies.forms.layout.RowSpec; - -public class MergeEntriesDialog extends JabRefDialog { - private static final String MERGE_ENTRIES = Localization.lang("Merge entries"); - private static final String MARGIN = "5px"; - private final BasePanel panel; +public class MergeEntriesDialog extends BaseDialog { - private final CellConstraints cc = new CellConstraints(); - private final DialogService dialogService; + private final MergeEntries mergeEntries; - public MergeEntriesDialog(BasePanel panel, DialogService dialogService) { - super(MERGE_ENTRIES, true, MergeEntriesDialog.class); - this.dialogService = dialogService; - this.panel = panel; + public MergeEntriesDialog(BibEntry one, BibEntry two, BibDatabaseMode databaseMode) { + mergeEntries = new MergeEntries(one, two, databaseMode); - // Start setting up the dialog - init(panel.getSelectedEntries()); + init(); } /** * Sets up the dialog * - * @param selected Selected BibtexEntries */ - private void init(List selected) { - - // Check if there are two entries selected - if (selected.size() != 2) { // None selected. Inform the user to select entries first. - - dialogService.showInformationDialogAndWait(Localization.lang("Merge entries"), - Localization.lang("You have to choose exactly two entries to merge.")); - - this.dispose(); - return; - } - - // Store the two entries - BibEntry one = selected.get(0); - BibEntry two = selected.get(1); - - MergeEntries mergeEntries = new MergeEntries(one, two, panel.getBibDatabaseContext().getMode()); - - // Create undo-compound - NamedCompound ce = new NamedCompound(MERGE_ENTRIES); - - FormLayout layout = new FormLayout("fill:700px:grow", "fill:400px:grow, 4px, p, 5px, p"); - this.setLayout(layout); - - this.add(mergeEntries.getMergeEntryPanel(), cc.xy(1, 1)); - this.add(new JSeparator(), cc.xy(1, 3)); + private void init() { + this.getDialogPane().setContent(mergeEntries); // Create buttons - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - JButton cancel = new JButton(Localization.lang("Cancel")); - cancel.setActionCommand("cancel"); - cancel.addActionListener(e -> { - panel.output(Localization.lang("Canceled merging entries")); - dispose(); - }); - - JButton replaceentries = new JButton(MERGE_ENTRIES); - replaceentries.setActionCommand("replace"); - replaceentries.addActionListener(e -> { - // Create a new entry and add it to the undo stack - // Remove the other two entries and add them to the undo stack (which is not working...) - BibEntry mergedEntry = mergeEntries.getMergeEntry(); - DefaultTaskExecutor.runInJavaFXThread(() -> { - panel.insertEntry(mergedEntry); - ce.addEdit(new UndoableInsertEntry(panel.getDatabase(), mergedEntry)); - ce.addEdit(new UndoableRemoveEntry(panel.getDatabase(), one, panel)); - panel.getDatabase().removeEntry(one); - ce.addEdit(new UndoableRemoveEntry(panel.getDatabase(), two, panel)); - panel.getDatabase().removeEntry(two); - ce.end(); - panel.getUndoManager().addEdit(ce); - panel.output(Localization.lang("Merged entries")); - }); - - dispose(); + ButtonType replaceEntries = new ButtonType(Localization.lang("Merge entries"), ButtonBar.ButtonData.OK_DONE); + this.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, replaceEntries); + this.setResultConverter(buttonType -> { + if (buttonType.equals(replaceEntries)) { + return mergeEntries.getMergeEntry(); + } else { + return null; + } }); + } - bb.addButton(new JButton[] {replaceentries, cancel}); - this.add(bb.getPanel(), cc.xy(1, 5)); - - // Add some margin around the layout - layout.appendRow(RowSpec.decode(MARGIN)); - layout.appendColumn(ColumnSpec.decode(MARGIN)); - layout.insertRow(1, RowSpec.decode(MARGIN)); - layout.insertColumn(1, ColumnSpec.decode(MARGIN)); - - WindowLocation pw = new WindowLocation(this, JabRefPreferences.MERGEENTRIES_POS_X, - JabRefPreferences.MERGEENTRIES_POS_Y, JabRefPreferences.MERGEENTRIES_SIZE_X, - JabRefPreferences.MERGEENTRIES_SIZE_Y); - pw.displayWindowAtStoredLocation(); + public void setLeftHeaderText(String leftHeaderText) { + mergeEntries.setLeftHeaderText(leftHeaderText); + } - // Show what we've got - setVisible(true); + public void setRightHeaderText(String rightHeaderText) { + mergeEntries.setRightHeaderText(rightHeaderText); } } diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeFetchedEntryDialog.java b/src/main/java/org/jabref/gui/mergeentries/MergeFetchedEntryDialog.java deleted file mode 100644 index 516f59f98b9..00000000000 --- a/src/main/java/org/jabref/gui/mergeentries/MergeFetchedEntryDialog.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.jabref.gui.mergeentries; - -import java.awt.event.ActionEvent; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.JButton; -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; -import org.jabref.gui.util.WindowLocation; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.InternalBibtexFields; -import org.jabref.preferences.JabRefPreferences; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.layout.CellConstraints; -import com.jgoodies.forms.layout.ColumnSpec; -import com.jgoodies.forms.layout.FormLayout; -import com.jgoodies.forms.layout.RowSpec; - -/** - * Dialog for merging Bibtex entry with fetched data - */ -public class MergeFetchedEntryDialog extends JabRefDialog { - - private static final String MARGIN = "5px"; - private final BasePanel panel; - private final CellConstraints cc = new CellConstraints(); - private final BibEntry originalEntry; - private final BibEntry fetchedEntry; - private NamedCompound ce; - private MergeEntries mergeEntries; - private final String type; - - - public MergeFetchedEntryDialog(BasePanel panel, BibEntry originalEntry, BibEntry fetchedEntry, String type) { - super(Localization.lang("Merge entry with %0 information", type), true, MergeFetchedEntryDialog.class); - - this.panel = panel; - this.originalEntry = originalEntry; - this.fetchedEntry = fetchedEntry; - this.type = type; - - // Start setting up the dialog - init(); - } - - /** - * Sets up the dialog - */ - private void init() { - mergeEntries = new MergeEntries(this.originalEntry, this.fetchedEntry, Localization.lang("Original entry"), - Localization.lang("Entry from %0", type), panel.getBibDatabaseContext().getMode()); - - // Create undo-compound - ce = new NamedCompound(Localization.lang("Merge entry with %0 information", type)); - - FormLayout layout = new FormLayout("fill:700px:grow", "fill:400px:grow, 4px, p, 5px, p"); - this.setLayout(layout); - - this.add(mergeEntries.getMergeEntryPanel(), cc.xy(1, 1)); - this.add(new JSeparator(), cc.xy(1, 3)); - - // Create buttons - ButtonBarBuilder bb = new ButtonBarBuilder(); - bb.addGlue(); - - JButton cancel = new JButton(new CancelAction()); - JButton replaceEntry = new JButton(new ReplaceAction()); - - bb.addButton(replaceEntry, cancel); - this.add(bb.getPanel(), cc.xy(1, 5)); - - // Add some margin around the layout - layout.appendRow(RowSpec.decode(MARGIN)); - layout.appendColumn(ColumnSpec.decode(MARGIN)); - layout.insertRow(1, RowSpec.decode(MARGIN)); - layout.insertColumn(1, ColumnSpec.decode(MARGIN)); - - WindowLocation pw = new WindowLocation(this, JabRefPreferences.MERGEENTRIES_POS_X, - JabRefPreferences.MERGEENTRIES_POS_Y, JabRefPreferences.MERGEENTRIES_SIZE_X, - JabRefPreferences.MERGEENTRIES_SIZE_Y); - pw.displayWindowAtStoredLocation(); - - } - - private class CancelAction extends AbstractAction { - CancelAction() { - putValue(Action.NAME, Localization.lang("Cancel")); - } - - @Override - public void actionPerformed(ActionEvent e) { - panel.output(Localization.lang("Canceled merging entries")); - dispose(); - } - } - - private class ReplaceAction extends AbstractAction { - ReplaceAction() { - putValue(Action.NAME, Localization.lang("Replace original entry")); - } - - @Override - public void actionPerformed(ActionEvent e) { - BibEntry mergedEntry = mergeEntries.getMergeEntry(); - - // Updated the original entry with the new fields - Set jointFields = new TreeSet<>(mergedEntry.getFieldNames()); - Set originalFields = new TreeSet<>(originalEntry.getFieldNames()); - boolean edited = false; - - // entry type - String oldType = originalEntry.getType(); - String newType = mergedEntry.getType(); - - if (!oldType.equalsIgnoreCase(newType)) { - originalEntry.setType(newType); - ce.addEdit(new UndoableChangeType(originalEntry, oldType, newType)); - edited = true; - } - - // fields - for (String field : jointFields) { - Optional originalString = originalEntry.getField(field); - Optional mergedString = mergedEntry.getField(field); - if (!originalString.isPresent() || !originalString.equals(mergedString)) { - originalEntry.setField(field, mergedString.get()); // mergedString always present - ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.orElse(null), - mergedString.get())); - edited = true; - } - } - - // Remove fields which are not in the merged entry, unless they are internal fields - for (String field : originalFields) { - if (!jointFields.contains(field) && !InternalBibtexFields.isInternalField(field)) { - Optional originalString = originalEntry.getField(field); - originalEntry.clearField(field); - ce.addEdit(new UndoableFieldChange(originalEntry, field, originalString.get(), null)); // originalString always present - edited = true; - } - } - - if (edited) { - ce.end(); - panel.getUndoManager().addEdit(ce); - panel.output(Localization.lang("Updated entry with info from %0", type)); - panel.updateEntryEditorIfShowing(); - panel.markBaseChanged(); - } else { - panel.output(Localization.lang("No information added")); - } - - dispose(); - } - } -} diff --git a/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java b/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java index a3a8a349d56..917b88b3ea4 100644 --- a/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java +++ b/src/main/java/org/jabref/gui/preferences/AppearancePrefsTab.java @@ -13,14 +13,13 @@ import org.jabref.gui.DialogService; import org.jabref.gui.util.ControlHelper; +import org.jabref.gui.util.ThemeLoader; import org.jabref.logic.l10n.Localization; import org.jabref.model.strings.StringUtil; import org.jabref.preferences.JabRefPreferences; class AppearancePrefsTab extends Pane implements PrefsTab { - public static final String BASE_CSS = "Base.css"; - public static final String DARK_CSS = "Dark.css"; private final JabRefPreferences prefs; private final CheckBox fontTweaksLAF; private final TextField fontSize; @@ -55,14 +54,13 @@ public AppearancePrefsTab(DialogService dialogService, JabRefPreferences prefs) darkTheme.setToggleGroup(themeGroup); String cssFileName = prefs.get(JabRefPreferences.FX_THEME); - if (StringUtil.isBlank(cssFileName) || BASE_CSS.equals(cssFileName)) { + if (StringUtil.isBlank(cssFileName) || ThemeLoader.MAIN_CSS.equalsIgnoreCase(cssFileName)) { lightTheme.setSelected(true); - } else if (DARK_CSS.equals(cssFileName)) { + } else if (ThemeLoader.DARK_CSS.equals(cssFileName)) { darkTheme.setSelected(true); } container.getChildren().addAll(overrideFonts, fontSizeContainer, fontTweaksLAF, lightTheme, darkTheme); - } public Node getBuilder() { @@ -90,11 +88,11 @@ public void storeSettings() { boolean isThemeChanged = false; - if (lightTheme.isSelected() && !prefs.get(JabRefPreferences.FX_THEME).equals(BASE_CSS)) { - prefs.put(JabRefPreferences.FX_THEME, BASE_CSS); + if (lightTheme.isSelected() && !prefs.get(JabRefPreferences.FX_THEME).equals(ThemeLoader.MAIN_CSS)) { + prefs.put(JabRefPreferences.FX_THEME, ThemeLoader.MAIN_CSS); isThemeChanged = true; - } else if (darkTheme.isSelected() && !prefs.get(JabRefPreferences.FX_THEME).equals(DARK_CSS)) { - prefs.put(JabRefPreferences.FX_THEME, DARK_CSS); + } else if (darkTheme.isSelected() && !prefs.get(JabRefPreferences.FX_THEME).equals(ThemeLoader.DARK_CSS)) { + prefs.put(JabRefPreferences.FX_THEME, ThemeLoader.DARK_CSS); isThemeChanged = true; } diff --git a/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java b/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java index ab874bf1287..dd04ed460cd 100644 --- a/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java +++ b/src/main/java/org/jabref/gui/preferences/EntryEditorPrefsTab.java @@ -6,6 +6,7 @@ import javafx.scene.control.RadioButton; import javafx.scene.control.Separator; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; @@ -102,18 +103,26 @@ public EntryEditorPrefsTab(JabRefPreferences prefs) { Label nameFormat = new Label(Localization.lang("Name format used for autocompletion")); nameFormat.getStyleClass().add("sectionHeader"); + final ToggleGroup autocompletionToggleGroup = new ToggleGroup(); builder.add(nameFormat, 1, 14); builder.add(autoCompFF, 1, 15); builder.add(autoCompLF, 1, 16); builder.add(autoCompBoth, 1, 17); + autoCompFF.setToggleGroup(autocompletionToggleGroup); + autoCompLF.setToggleGroup(autocompletionToggleGroup); + autoCompBoth.setToggleGroup(autocompletionToggleGroup); builder.add(new Label(""), 1, 18); Label treatment = new Label(Localization.lang("Treatment of first names")); treatment.getStyleClass().add("sectionHeader"); + final ToggleGroup treatmentOfFirstNamesToggleGroup = new ToggleGroup(); builder.add(treatment, 1, 19); builder.add(firstNameModeAbbr, 1, 20); builder.add(firstNameModeFull, 1, 21); builder.add(firstNameModeBoth, 1, 22); + firstNameModeAbbr.setToggleGroup(treatmentOfFirstNamesToggleGroup); + firstNameModeFull.setToggleGroup(treatmentOfFirstNamesToggleGroup); + firstNameModeBoth.setToggleGroup(treatmentOfFirstNamesToggleGroup); } @Override diff --git a/src/main/java/org/jabref/gui/preferences/ExternalTab.java b/src/main/java/org/jabref/gui/preferences/ExternalTab.java index a9ec0064a42..782a14fa11d 100644 --- a/src/main/java/org/jabref/gui/preferences/ExternalTab.java +++ b/src/main/java/org/jabref/gui/preferences/ExternalTab.java @@ -13,6 +13,7 @@ import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; import org.jabref.Globals; @@ -69,6 +70,9 @@ public ExternalTab(JabRefFrame frame, PreferencesDialog prefsDiag, JabRefPrefere browseAdobeAcrobatReader.setOnAction(e -> showAdobeChooser()); GridPane consoleOptionPanel = new GridPane(); + final ToggleGroup consoleGroup = new ToggleGroup(); + defaultConsole.setToggleGroup(consoleGroup); + executeConsole.setToggleGroup(consoleGroup); consoleOptionPanel.add(defaultConsole, 1, 1); consoleOptionPanel.add(executeConsole, 1, 2); consoleOptionPanel.add(consoleCommand, 2, 2); @@ -76,13 +80,16 @@ public ExternalTab(JabRefFrame frame, PreferencesDialog prefsDiag, JabRefPrefere consoleOptionPanel.add(commandDescription, 2, 3); GridPane pdfOptionPanel = new GridPane(); + final ToggleGroup pdfReaderGroup = new ToggleGroup(); pdfOptionPanel.add(adobeAcrobatReader, 1, 1); pdfOptionPanel.add(adobeAcrobatReaderPath, 2, 1); + adobeAcrobatReader.setToggleGroup(pdfReaderGroup); pdfOptionPanel.add(browseAdobeAcrobatReader, 3, 1); if (OS.WINDOWS) { browseSumatraReader.setOnAction(e -> showSumatraChooser()); pdfOptionPanel.add(sumatraReader, 1, 2); + sumatraReader.setToggleGroup(pdfReaderGroup); pdfOptionPanel.add(sumatraReaderPath, 2, 2); pdfOptionPanel.add(browseSumatraReader, 3, 2); } diff --git a/src/main/java/org/jabref/gui/preferences/FileTab.java b/src/main/java/org/jabref/gui/preferences/FileTab.java index 110caa3f640..55e599e67ae 100644 --- a/src/main/java/org/jabref/gui/preferences/FileTab.java +++ b/src/main/java/org/jabref/gui/preferences/FileTab.java @@ -12,6 +12,7 @@ import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; @@ -95,11 +96,13 @@ public FileTab(DialogService dialogService, JabRefPreferences prefs) { builder.add(backup, 1, 3); Label label = new Label(Localization.lang("Do not wrap the following fields when saving") + ":"); builder.add(label, 1, 4); + final ToggleGroup resolveGroup = new ToggleGroup(); builder.add(nonWrappableFields, 2, 4); builder.add(resolveStringsStandard, 1, 5); builder.add(resolveStringsAll, 1, 6); builder.add(doNotResolveStringsFor, 2, 6); - + resolveStringsStandard.setToggleGroup(resolveGroup); + resolveStringsAll.setToggleGroup(resolveGroup); Label newlineSeparatorLabel = new Label(Localization.lang("Newline separator") + ":"); builder.add(newlineSeparatorLabel, 1, 7); builder.add(newlineSeparator, 2, 7); @@ -125,10 +128,14 @@ public FileTab(DialogService dialogService, JabRefPreferences prefs) { }); builder.add(browse, 3, 12); builder.add(bibLocAsPrimaryDir, 1, 13); + final ToggleGroup autolinkGroup = new ToggleGroup(); builder.add(matchStartsWithKey, 1, 14); builder.add(matchExactKeyOnly, 1, 15); builder.add(useRegExpComboBox, 1, 16); builder.add(regExpTextField, 2, 16); + matchStartsWithKey.setToggleGroup(autolinkGroup); + matchExactKeyOnly.setToggleGroup(autolinkGroup); + useRegExpComboBox.setToggleGroup(autolinkGroup); Button help = new Button("?"); help.setOnAction(event -> new HelpAction(Localization.lang("Help on regular expression search"), diff --git a/src/main/java/org/jabref/gui/preferences/GroupsPrefsTab.java b/src/main/java/org/jabref/gui/preferences/GroupsPrefsTab.java index 989f4b534cc..e7086e15e0b 100644 --- a/src/main/java/org/jabref/gui/preferences/GroupsPrefsTab.java +++ b/src/main/java/org/jabref/gui/preferences/GroupsPrefsTab.java @@ -7,6 +7,7 @@ import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; @@ -45,8 +46,11 @@ public void handle(ActionEvent event) { builder.add(view, 1, 1); builder.add(hideNonHits, 2, 2); builder.add(grayOut, 2, 3); + final ToggleGroup selectionModeGroup = new ToggleGroup(); builder.add(multiSelectionModeIntersection, 2, 4); builder.add(multiSelectionModeUnion, 2, 5); + multiSelectionModeIntersection.setToggleGroup(selectionModeGroup); + multiSelectionModeUnion.setToggleGroup(selectionModeGroup); builder.add(autoAssignGroup, 2, 6); builder.add(new Label(""), 1, 7); diff --git a/src/main/java/org/jabref/gui/preferences/ImportSettingsTab.java b/src/main/java/org/jabref/gui/preferences/ImportSettingsTab.java index ad63acc2ffa..d2dd0896a7e 100644 --- a/src/main/java/org/jabref/gui/preferences/ImportSettingsTab.java +++ b/src/main/java/org/jabref/gui/preferences/ImportSettingsTab.java @@ -10,6 +10,7 @@ import javafx.scene.control.RadioButton; import javafx.scene.control.Separator; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; @@ -60,11 +61,16 @@ public ImportSettingsTab(JabRefPreferences prefs) { Label defaultImportStyle = new Label(Localization.lang("Default import style for drag and drop of PDFs")); defaultImportStyle.getStyleClass().add("sectionHeader"); builder.add(defaultImportStyle, 1, 1); + final ToggleGroup defaultImportStyleDragDropPdfs = new ToggleGroup(); builder.add(new Separator(), 2, 1); builder.add(radioButtonNoMeta, 2, 2); builder.add(radioButtonXmp, 2, 3); builder.add(radioButtonPDFcontent, 2, 4); builder.add(radioButtononlyAttachPDF, 2, 5); + radioButtonNoMeta.setToggleGroup(defaultImportStyleDragDropPdfs); + radioButtonXmp.setToggleGroup(defaultImportStyleDragDropPdfs); + radioButtonPDFcontent.setToggleGroup(defaultImportStyleDragDropPdfs); + radioButtononlyAttachPDF.setToggleGroup(defaultImportStyleDragDropPdfs); builder.add(useDefaultPDFImportStyle, 2, 6); builder.add(new Label(""), 1, 7); diff --git a/src/main/java/org/jabref/gui/preferences/TableColumnsTab.java b/src/main/java/org/jabref/gui/preferences/TableColumnsTab.java index 9a9ea48bd0b..40ae74463ed 100644 --- a/src/main/java/org/jabref/gui/preferences/TableColumnsTab.java +++ b/src/main/java/org/jabref/gui/preferences/TableColumnsTab.java @@ -24,6 +24,7 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.layout.BorderPane; @@ -284,14 +285,20 @@ public TableColumnsTab(JabRefPreferences prefs, JabRefFrame frame) { specialTableColumnsBuilder.add(priorityColumn, 1, 5); specialTableColumnsBuilder.add(printedColumn, 1, 6); specialTableColumnsBuilder.add(readStatusColumn, 1, 7); + final ToggleGroup syncGroup = new ToggleGroup(); specialTableColumnsBuilder.add(syncKeywords, 1, 8); specialTableColumnsBuilder.add(writeSpecialFields, 1, 9); + syncKeywords.setToggleGroup(syncGroup); + writeSpecialFields.setToggleGroup(syncGroup); specialTableColumnsBuilder.add(helpButton, 1, 10); specialTableColumnsBuilder.add(fileColumn, 2, 1); specialTableColumnsBuilder.add(urlColumn, 2, 2); + final ToggleGroup preferUrlOrDoi = new ToggleGroup(); specialTableColumnsBuilder.add(preferUrl, 2 ,3); specialTableColumnsBuilder.add(preferDoi, 2, 4); + preferUrl.setToggleGroup(preferUrlOrDoi); + preferDoi.setToggleGroup(preferUrlOrDoi); specialTableColumnsBuilder.add(arxivColumn, 2, 5); specialTableColumnsBuilder.add(extraFileColumns,2, 6); diff --git a/src/main/java/org/jabref/gui/preferences/TablePrefsTab.java b/src/main/java/org/jabref/gui/preferences/TablePrefsTab.java index 4ec10405351..c0dfe9b90be 100644 --- a/src/main/java/org/jabref/gui/preferences/TablePrefsTab.java +++ b/src/main/java/org/jabref/gui/preferences/TablePrefsTab.java @@ -4,6 +4,7 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; @@ -54,26 +55,33 @@ public TablePrefsTab(JabRefPreferences prefs) { Label formatOfAuthor = new Label(Localization.lang("Format of author and editor names")); formatOfAuthor.getStyleClass().add("sectionHeader"); builder.add(formatOfAuthor, 1, 1); + final ToggleGroup formatNamesToggleGroup = new ToggleGroup(); + final ToggleGroup nameAbbrevToggleGroup = new ToggleGroup(); builder.add(namesAsIs, 1, 2); + namesAsIs.setToggleGroup(formatNamesToggleGroup); builder.add(noAbbrNames, 2, 2); + noAbbrNames.setToggleGroup(nameAbbrevToggleGroup); builder.add(namesFf, 1, 3); + namesFf.setToggleGroup(formatNamesToggleGroup); builder.add(abbrNames, 2, 3); + abbrNames.setToggleGroup(nameAbbrevToggleGroup); builder.add(namesFl, 1, 4); + namesFl.setToggleGroup(formatNamesToggleGroup); builder.add(lastNamesOnly, 2, 4); + lastNamesOnly.setToggleGroup(nameAbbrevToggleGroup); builder.add(namesNatbib, 1, 5); - + namesNatbib.setToggleGroup(formatNamesToggleGroup); Label label1 = new Label(""); builder.add(label1, 1, 6); - Label general = new Label(Localization.lang("General")); general.getStyleClass().add("sectionHeader"); builder.add(general, 1, 7); builder.add(autoResizeMode, 1, 8); - namesNatbib.setOnAction(e -> { - abbrNames.setDisable(namesNatbib.isSelected()); - lastNamesOnly.setDisable(namesNatbib.isSelected()); - noAbbrNames.setDisable(namesNatbib.isSelected()); - }); + + abbrNames.disableProperty().bind(namesNatbib.selectedProperty()); + lastNamesOnly.disableProperty().bind(namesNatbib.selectedProperty()); + noAbbrNames.disableProperty().bind(namesNatbib.selectedProperty()); + } @Override @@ -102,10 +110,6 @@ public void setValues() { noAbbrNames.setSelected(true); } - abbrNames.setDisable(namesNatbib.isSelected()); - lastNamesOnly.setDisable(namesNatbib.isSelected()); - noAbbrNames.setDisable(namesNatbib.isSelected()); - } /** diff --git a/src/main/java/org/jabref/gui/shared/MergeSharedEntryDialog.java b/src/main/java/org/jabref/gui/shared/MergeSharedEntryDialog.java index e2ac256b025..44a26afd33b 100644 --- a/src/main/java/org/jabref/gui/shared/MergeSharedEntryDialog.java +++ b/src/main/java/org/jabref/gui/shared/MergeSharedEntryDialog.java @@ -14,7 +14,10 @@ import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; +import javafx.scene.Scene; + import org.jabref.gui.JabRefFrame; +import org.jabref.gui.customjfx.CustomJFXPanel; import org.jabref.gui.mergeentries.MergeEntries; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseMode; @@ -65,7 +68,7 @@ public void showMergeDialog() { mergeInnformation.setBorder(new EmptyBorder(9, 9, 9, 9)); mergeDialog.add(mergeInnformation, BorderLayout.NORTH); - mergeDialog.add(mergeEntries.getMergeEntryPanel(), BorderLayout.CENTER); + mergeDialog.add(CustomJFXPanel.wrap(new Scene(mergeEntries)), BorderLayout.CENTER); JButton mergeButton = new JButton(Localization.lang("Merge entries")); mergeButton.addActionListener(e -> mergeEntries()); diff --git a/src/main/java/org/jabref/gui/util/BaseDialog.java b/src/main/java/org/jabref/gui/util/BaseDialog.java index 41dda14d551..59f6e034e3b 100644 --- a/src/main/java/org/jabref/gui/util/BaseDialog.java +++ b/src/main/java/org/jabref/gui/util/BaseDialog.java @@ -21,7 +21,7 @@ protected BaseDialog() { setDialogIcon(IconTheme.getJabRefImageFX()); setResizable(true); - Globals.getThemeLoader().installBaseCss(getDialogPane().getScene(), Globals.prefs); + Globals.getThemeLoader().installCss(getDialogPane().getScene(), Globals.prefs); } private void setDialogIcon(Image image) { diff --git a/src/main/java/org/jabref/gui/util/ThemeLoader.java b/src/main/java/org/jabref/gui/util/ThemeLoader.java index 9c4e84ce3f6..d7d452e2b3e 100644 --- a/src/main/java/org/jabref/gui/util/ThemeLoader.java +++ b/src/main/java/org/jabref/gui/util/ThemeLoader.java @@ -1,13 +1,15 @@ package org.jabref.gui.util; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; +import java.util.Optional; -import javafx.scene.Parent; import javafx.scene.Scene; import org.jabref.gui.JabRefFrame; @@ -34,79 +36,78 @@ */ public class ThemeLoader { - public static final String DEFAULT_MAIN_CSS = "Base.css"; - private static final String DEFAULT_PATH_MAIN_CSS = JabRefFrame.class.getResource(DEFAULT_MAIN_CSS).toExternalForm(); + public static final String MAIN_CSS = "Base.css"; + public static final String DARK_CSS = "Dark.css"; + private static final Logger LOGGER = LoggerFactory.getLogger(ThemeLoader.class); - private String cssToLoad = System.getProperty("jabref.theme.css"); + private final Optional additionalCssToLoad; private final FileUpdateMonitor fileUpdateMonitor; public ThemeLoader(FileUpdateMonitor fileUpdateMonitor, JabRefPreferences jabRefPreferences) { this.fileUpdateMonitor = Objects.requireNonNull(fileUpdateMonitor); - if (!StringUtil.isNullOrEmpty(cssToLoad)) { - LOGGER.info("using css from system " + cssToLoad); - return; - } - - // otherwise load css from preference - String cssFileName = jabRefPreferences.get(JabRefPreferences.FX_THEME); - if (cssFileName != null) { + String cssVmArgument = System.getProperty("jabref.theme.css"); + String cssPreferences = jabRefPreferences.get(JabRefPreferences.FX_THEME); + if (StringUtil.isNotBlank(cssVmArgument)) { + // First priority: VM argument + LOGGER.info("Using css from VM option: " + cssVmArgument); + URL cssVmUrl = null; try { - cssToLoad = JabRefFrame.class.getResource(cssFileName).toExternalForm(); - LOGGER.info("using css " + cssToLoad); - } catch (Exception e) { - LOGGER.warn("can't get css file path of " + cssFileName); + cssVmUrl = Paths.get(cssVmArgument).toUri().toURL(); + } catch (MalformedURLException e) { + LOGGER.warn("Cannot load css " + cssVmArgument, e); + } + additionalCssToLoad = Optional.ofNullable(cssVmUrl); + } else if (StringUtil.isNotBlank(cssPreferences) && !MAIN_CSS.equalsIgnoreCase(cssPreferences)) { + // Otherwise load css from preference + URL cssResource = JabRefFrame.class.getResource(cssPreferences); + if (cssResource != null) { + LOGGER.debug("Using css " + cssResource); + additionalCssToLoad = Optional.of(cssResource); + } else { + additionalCssToLoad = Optional.empty(); + LOGGER.warn("Cannot load css " + cssPreferences); } + } else { + additionalCssToLoad = Optional.empty(); } } - /** - * Installs the base css file as a stylesheet in the given scene. - * Changes in the css file lead to a redraw of the scene using the new css file. + * Installs the base css file as a stylesheet in the given scene. Changes in the css file lead to a redraw of the + * scene using the new css file. */ - public void installBaseCss(Scene scene, JabRefPreferences preferences) { - if (!StringUtil.isNullOrEmpty(cssToLoad)) { - addAndWatchForChanges(scene, cssToLoad, 0); - } else { - LOGGER.warn("using the last default css " + DEFAULT_PATH_MAIN_CSS); - addAndWatchForChanges(scene, DEFAULT_PATH_MAIN_CSS, 0); - } + public void installCss(Scene scene, JabRefPreferences preferences) { + addAndWatchForChanges(scene, JabRefFrame.class.getResource(MAIN_CSS), 0); + additionalCssToLoad.ifPresent(file -> addAndWatchForChanges(scene, file, 1)); preferences.getFontSize().ifPresent(size -> scene.getRoot().setStyle("-fx-font-size: " + size + "pt;")); } - private void addAndWatchForChanges(Scene scene, String cssUrl, int index) { - // avoid repeat add - if (scene.getStylesheets().contains(cssUrl)) return; - - scene.getStylesheets().add(index, cssUrl); + private void addAndWatchForChanges(Scene scene, URL cssFile, int index) { + scene.getStylesheets().add(index, cssFile.toExternalForm()); try { - // If -Djabref.theme.css is defined and the resources are not part of a .jar bundle, - // we watch the file for changes and turn on live reloading - if (!cssUrl.startsWith("jar:")) { - Path cssFile = Paths.get(new URL(cssUrl).toURI()); - LOGGER.info("Enabling live reloading of " + cssFile); - fileUpdateMonitor.addListenerForFile(cssFile, () -> { - LOGGER.info("Reload css file " + cssFile); - DefaultTaskExecutor.runInJavaFXThread(() -> { - scene.getStylesheets().remove(cssUrl); - scene.getStylesheets().add(index, cssUrl); - } - ); - }); + + URI cssUri = cssFile.toURI(); + if (!cssUri.toString().contains("jar")) { + LOGGER.debug("CSS URI {}", cssUri); + + Path cssPath = Paths.get(cssUri).toAbsolutePath(); + // If the file is an ordinary file (i.e. not a resource part of a .jar bundle), we watch it for changes and turn on live reloading + if (!cssUri.toString().contains("jar")) { + LOGGER.info("Enabling live reloading of {}", cssPath); + fileUpdateMonitor.addListenerForFile(cssPath, () -> { + LOGGER.info("Reload css file " + cssFile); + DefaultTaskExecutor.runInJavaFXThread(() -> { + scene.getStylesheets().remove(cssFile.toExternalForm()); + scene.getStylesheets().add(index, cssFile.toExternalForm()); + }); + }); + } } - } catch (URISyntaxException | IOException e) { - LOGGER.error("Could not watch css file for changes " + cssUrl, e); + } catch (IOException | URISyntaxException e) { + LOGGER.error("Could not watch css file for changes " + cssFile, e); } } - - /** - * @deprecated you should never need to add css to a control, add it to the scene containing the control - */ - @Deprecated - public void installBaseCss(Parent control) { - control.getStylesheets().add(0, DEFAULT_PATH_MAIN_CSS); - } } diff --git a/src/main/java/org/jabref/gui/util/component/DiffHighlightingTextPane.java b/src/main/java/org/jabref/gui/util/component/DiffHighlightingTextPane.java index e02eb7d080a..496feb1e5ee 100644 --- a/src/main/java/org/jabref/gui/util/component/DiffHighlightingTextPane.java +++ b/src/main/java/org/jabref/gui/util/component/DiffHighlightingTextPane.java @@ -1,10 +1,8 @@ package org.jabref.gui.util.component; -import javax.swing.JTextPane; -import javax.swing.text.html.HTMLEditorKit; -import javax.swing.text.html.StyleSheet; +import javafx.scene.text.TextFlow; -public class DiffHighlightingTextPane extends JTextPane { +public class DiffHighlightingTextPane extends TextFlow { private static final String BODY_STYLE = "body{font:sans-serif}"; private static final String ADDITION_STYLE = ".add{color:blue;text-decoration:underline}"; @@ -16,13 +14,13 @@ public class DiffHighlightingTextPane extends JTextPane { public DiffHighlightingTextPane() { super(); - setContentType(CONTENT_TYPE); - StyleSheet sheet = ((HTMLEditorKit) getEditorKit()).getStyleSheet(); - sheet.addRule(BODY_STYLE); - sheet.addRule(ADDITION_STYLE); - sheet.addRule(REMOVAL_STYLE); - sheet.addRule(CHANGE_STYLE); - setEditable(false); +// setContentType(CONTENT_TYPE); +// StyleSheet sheet = ((HTMLEditorKit) getEditorKit()).getStyleSheet(); +// sheet.addRule(BODY_STYLE); +// sheet.addRule(ADDITION_STYLE); +// sheet.addRule(REMOVAL_STYLE); +// sheet.addRule(CHANGE_STYLE); +// setEditable(false); } } diff --git a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java index 7ce1e57e6f6..cda929568d2 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java +++ b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java @@ -64,7 +64,8 @@ public synchronized List makeBibliography(List bibEntries, Str */ private void initialize(String newStyle, CitationStyleOutputFormat newFormat) throws IOException { if (cslInstance == null || !Objects.equals(newStyle, style)) { - cslInstance = new CSL(dataProvider, newStyle); + // lang and forceLang are set to the default values of other CSL constructors + cslInstance = new CSL(dataProvider, new JabRefLocaleProvider(), newStyle, "en-US", false); style = newStyle; } diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStyle.java b/src/main/java/org/jabref/logic/citationstyle/CitationStyle.java index 02010cbbea6..f1ebe894836 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStyle.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStyle.java @@ -2,7 +2,7 @@ import java.io.IOException; import java.io.StringReader; -import java.io.UncheckedIOException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -13,12 +13,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -39,15 +37,13 @@ import org.xml.sax.SAXException; /** - * Representation of a CitationStyle. - * Stores its name, the file path and the style itself + * Representation of a CitationStyle. Stores its name, the file path and the style itself */ public class CitationStyle { public static final String DEFAULT = "/ieee.csl"; private static final Logger LOGGER = LoggerFactory.getLogger(CitationStyle.class); - - private static final Pattern SNAPSHOT_NAME = Pattern.compile(".*styles-1\\.0\\.1-SNAPSHOT\\.jar"); + private static final String STYLES_ROOT = "/csl-styles"; private static final List STYLES = new ArrayList<>(); @@ -88,7 +84,7 @@ private static Optional createCitationStyleFromSource(final Strin private static String stripInvalidProlog(String source) { int startIndex = source.indexOf("<"); if (startIndex > 0) { - return source.substring(startIndex, source.length()); + return source.substring(startIndex); } else { return source; } @@ -99,13 +95,13 @@ private static String stripInvalidProlog(String source) { */ public static Optional createCitationStyleFromFile(final String styleFile) { if (!isCitationStyleFile(styleFile)) { - LOGGER.error("Can only load style files: " + styleFile); + LOGGER.error("Can only load style files: {}", styleFile); return Optional.empty(); } try { String text; - String internalFile = (styleFile.startsWith("/") ? "" : "/") + styleFile; + String internalFile = STYLES_ROOT + (styleFile.startsWith("/") ? "" : "/") + styleFile; URL url = CitationStyle.class.getResource(internalFile); if (url != null) { text = CSLUtils.readURLToString(url, StandardCharsets.UTF_8.toString()); @@ -140,36 +136,37 @@ public static List discoverCitationStyles() { if (!STYLES.isEmpty()) { return STYLES; } - try { - - Path filePath = Paths.get(CitationStyle.class.getProtectionDomain().getCodeSource().getLocation().toURI()); - String path = filePath.toString(); - - // This is a quick fix to have the styles when running JabRef in a development environment. - // The styles.jar is not extracted into the JabRef.jar and therefore, we search the classpath for it. - if (Files.isDirectory(filePath)) { - final String cp = System.getProperty("java.class.path"); - final String[] entries = cp.split(System.getProperty("path.separator")); - - Optional foundStyle = Arrays.stream(entries).filter(entry -> SNAPSHOT_NAME.matcher(entry).matches()).findFirst(); - path = foundStyle.orElse(path); - } - - try (FileSystem jarFs = FileSystems.newFileSystem(Paths.get(path), null)) { - try (Stream stylefileStream = Files.find(jarFs.getRootDirectories().iterator().next(), 1, (file, attr) -> file.toString().endsWith("csl"))) { - for (Path style : stylefileStream.collect(Collectors.toList())) { - CitationStyle.createCitationStyleFromFile(style.getFileName().toString()).ifPresent(STYLES::add); - } - } catch (UncheckedIOException e) { - throw new IOException(e); + URL url = CitationStyle.class.getResource(STYLES_ROOT); + if (url == null) { + return Collections.emptyList(); + } + try { + URI uri = url.toURI(); + if ("jar".equals(uri.getScheme())) { + try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { + Path path = fs.getPath(STYLES_ROOT); + STYLES.addAll(discoverCitationStylesInPath(path)); } + } else { + STYLES.addAll(discoverCitationStylesInPath(Paths.get(uri))); } return STYLES; - } catch (UncheckedIOException | IOException | URISyntaxException ex) { - LOGGER.error("something went wrong while searching available CitationStyles. Are you running directly from source code?", ex); + } catch (URISyntaxException | IOException e) { + LOGGER.error("something went wrong while searching available CitationStyles. Are you running directly from source code?", e); + return Collections.emptyList(); + } + } + + private static List discoverCitationStylesInPath(Path path) throws IOException { + try (Stream stream = Files.find(path, 1, (file, attr) -> file.toString().endsWith("csl"))) { + return stream.map(Path::getFileName) + .map(Path::toString) + .map(CitationStyle::createCitationStyleFromFile) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); } - return Collections.emptyList(); } /** diff --git a/src/main/java/org/jabref/logic/citationstyle/JabRefLocaleProvider.java b/src/main/java/org/jabref/logic/citationstyle/JabRefLocaleProvider.java new file mode 100644 index 00000000000..ecfc18d55de --- /dev/null +++ b/src/main/java/org/jabref/logic/citationstyle/JabRefLocaleProvider.java @@ -0,0 +1,38 @@ +package org.jabref.logic.citationstyle; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import de.undercouch.citeproc.LocaleProvider; +import de.undercouch.citeproc.helper.CSLUtils; + +/** + * A {@link LocaleProvider} that loads locales from a directory in the current module. + * + * This implementation is only a slight adaption of {@link de.undercouch.citeproc.DefaultLocaleProvider}. + */ +public class JabRefLocaleProvider implements LocaleProvider { + + private static final String LOCALES_ROOT = "/csl-locales"; + + private final Map locales = new HashMap<>(); + + @Override + public String retrieveLocale(String lang) { + return locales.computeIfAbsent(lang, locale -> { + try { + URL url = getClass().getResource(LOCALES_ROOT + "/locales-" + locale + ".xml"); + if (url == null) { + throw new IllegalArgumentException("Unable to load locale " + locale); + } + + return CSLUtils.readURLToString(url, "UTF-8"); + } catch (IOException e) { + throw new UncheckedIOException("failed to read locale " + locale, e); + } + }); + } +} diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index 0deb118292e..0fd2880a30d 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -2,7 +2,7 @@ import java.io.IOException; import java.net.URL; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -22,6 +22,7 @@ import org.jabref.model.entry.FieldName; import org.jabref.model.entry.identifier.DOI; import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.model.util.OptionalUtil; public class DoiFetcher implements IdBasedFetcher, EntryBasedFetcher { public static final String NAME = "DOI"; @@ -75,9 +76,11 @@ private void doPostCleanup(BibEntry entry) { @Override public List performSearch(BibEntry entry) throws FetcherException { - Optional bibEntry = performSearchById(entry.getField(FieldName.DOI).orElse("")); - List list = new ArrayList<>(); - bibEntry.ifPresent(list::add); - return list; + Optional doi = entry.getField(FieldName.DOI); + if (doi.isPresent()) { + return OptionalUtil.toList(performSearchById(doi.get())); + } else { + return Collections.emptyList(); + } } } diff --git a/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java index 67020c721fc..73d5f0544fc 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java @@ -45,13 +45,14 @@ public class PdfContentImporter extends Importer { // input lines into several lines private String[] lines; // current index in lines - private int i; + private int lineIndex; private String curString; private String year; public PdfContentImporter(ImportFormatPreferences importFormatPreferences) { this.importFormatPreferences = importFormatPreferences; + } /** * Removes all non-letter characters at the end @@ -225,17 +226,19 @@ public ParserResult importDatabase(Path filePath, Charset defaultEncoding) { // the different lines are joined into one and thereby separated by " " lines = firstPageContents.split(System.lineSeparator()); + lineIndex = 0; //to prevent array index out of bounds exception on second run we need to reset i to zero + proceedToNextNonEmptyLine(); - if (i >= lines.length) { + if (lineIndex >= lines.length) { // PDF could not be parsed or is empty // return empty list return new ParserResult(); } // we start at the current line - curString = lines[i]; + curString = lines[lineIndex]; // i might get incremented later and curString modified, too - i = i + 1; + lineIndex = lineIndex + 1; String author; String editor = null; @@ -279,10 +282,10 @@ public ParserResult importDatabase(Path filePath, Charset defaultEncoding) { // after title: authors author = null; - while ((i < lines.length) && !"".equals(lines[i])) { + while ((lineIndex < lines.length) && !"".equals(lines[lineIndex])) { // author names are unlikely to be lines among different lines // treat them line by line - curString = streamlineNames(lines[i]); + curString = streamlineNames(lines[lineIndex]); if (author == null) { author = curString; } else { @@ -292,14 +295,14 @@ public ParserResult importDatabase(Path filePath, Charset defaultEncoding) { author = author.concat(" and ").concat(curString); } } - i++; + lineIndex++; } curString = ""; - i++; + lineIndex++; // then, abstract and keywords follow - while (i < lines.length) { - curString = lines[i]; + while (lineIndex < lines.length) { + curString = lines[lineIndex]; if ((curString.length() >= "Abstract".length()) && "Abstract".equalsIgnoreCase(curString.substring(0, "Abstract".length()))) { if (curString.length() == "Abstract".length()) { // only word "abstract" found -- skip line @@ -307,15 +310,15 @@ public ParserResult importDatabase(Path filePath, Charset defaultEncoding) { } else { curString = curString.substring("Abstract".length() + 1).trim().concat(System.lineSeparator()); } - i++; + lineIndex++; // fillCurStringWithNonEmptyLines() cannot be used as that uses " " as line separator // whereas we need linebreak as separator - while ((i < lines.length) && !"".equals(lines[i])) { - curString = curString.concat(lines[i]).concat(System.lineSeparator()); - i++; + while ((lineIndex < lines.length) && !"".equals(lines[lineIndex])) { + curString = curString.concat(lines[lineIndex]).concat(System.lineSeparator()); + lineIndex++; } abstractT = curString.trim(); - i++; + lineIndex++; } else if ((curString.length() >= "Keywords".length()) && "Keywords".equalsIgnoreCase(curString.substring(0, "Keywords".length()))) { if (curString.length() == "Keywords".length()) { // only word "Keywords" found -- skip line @@ -323,7 +326,7 @@ public ParserResult importDatabase(Path filePath, Charset defaultEncoding) { } else { curString = curString.substring("Keywords".length() + 1).trim(); } - i++; + lineIndex++; fillCurStringWithNonEmptyLines(); keywords = removeNonLettersAtEnd(curString); } else { @@ -340,18 +343,18 @@ public ParserResult importDatabase(Path filePath, Charset defaultEncoding) { } } - i++; + lineIndex++; proceedToNextNonEmptyLine(); } } - i = lines.length - 1; + lineIndex = lines.length - 1; // last block: DOI, detailed information // sometimes, this information is in the third last block etc... // therefore, read until the beginning of the file - while (i >= 0) { + while (lineIndex >= 0) { readLastBlock(); // i now points to the block before or is -1 // curString contains the last block, separated by " " @@ -522,8 +525,8 @@ private void extractYear() { * proceed to next non-empty line */ private void proceedToNextNonEmptyLine() { - while ((i < lines.length) && "".equals(lines[i].trim())) { - i++; + while ((lineIndex < lines.length) && "".equals(lines[lineIndex].trim())) { + lineIndex++; } } @@ -540,16 +543,16 @@ private void proceedToNextNonEmptyLine() { private void fillCurStringWithNonEmptyLines() { // ensure that curString does not end with " " curString = curString.trim(); - while ((i < lines.length) && !"".equals(lines[i])) { - String curLine = lines[i].trim(); + while ((lineIndex < lines.length) && !"".equals(lines[lineIndex])) { + String curLine = lines[lineIndex].trim(); if (!"".equals(curLine)) { if (!curString.isEmpty()) { // insert separating space if necessary curString = curString.concat(" "); } - curString = curString.concat(lines[i]); + curString = curString.concat(lines[lineIndex]); } - i++; + lineIndex++; } proceedToNextNonEmptyLine(); @@ -563,22 +566,22 @@ private void fillCurStringWithNonEmptyLines() { * invariant before/after: i points to line before the last handled block */ private void readLastBlock() { - while ((i >= 0) && "".equals(lines[i].trim())) { - i--; + while ((lineIndex >= 0) && "".equals(lines[lineIndex].trim())) { + lineIndex--; } // i is now at the end of a block - int end = i; + int end = lineIndex; // find beginning - while ((i >= 0) && !"".equals(lines[i])) { - i--; + while ((lineIndex >= 0) && !"".equals(lines[lineIndex])) { + lineIndex--; } // i is now the line before the beginning of the block // this fulfills the invariant curString = ""; - for (int j = i + 1; j <= end; j++) { + for (int j = lineIndex + 1; j <= end; j++) { curString = curString.concat(lines[j].trim()); if (j != end) { curString = curString.concat(" "); diff --git a/src/main/java/org/jabref/logic/integrity/IntegrityMessage.java b/src/main/java/org/jabref/logic/integrity/IntegrityMessage.java index c04a63f87fa..f11fa936187 100644 --- a/src/main/java/org/jabref/logic/integrity/IntegrityMessage.java +++ b/src/main/java/org/jabref/logic/integrity/IntegrityMessage.java @@ -41,8 +41,12 @@ public Object clone() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } IntegrityMessage that = (IntegrityMessage) o; return Objects.equals(entry, that.entry) && Objects.equals(fieldName, that.fieldName) && @@ -53,5 +57,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(entry, fieldName, message); } - } diff --git a/src/main/java/org/jabref/logic/util/strings/DiffHighlighting.java b/src/main/java/org/jabref/logic/util/strings/DiffHighlighting.java deleted file mode 100644 index acd358d99e4..00000000000 --- a/src/main/java/org/jabref/logic/util/strings/DiffHighlighting.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.jabref.logic.util.strings; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import difflib.Delta; -import difflib.DiffUtils; - -public class DiffHighlighting { - - public static final String HTML_START = ""; - public static final String HTML_END = ""; - private static final String ADDITION_START = ""; - private static final String REMOVAL_START = ""; - private static final String CHANGE_START = ""; - - private static final String TAG_END = ""; - - private DiffHighlighting() { - } - - public static String generateDiffHighlighting(String baseString, String modifiedString, String separator) { - Objects.requireNonNull(separator); - if ((baseString != null) && (modifiedString != null)) { - List stringList = new ArrayList<>(Arrays.asList(baseString.split(separator))); - List> deltaList = new ArrayList<>( - DiffUtils.diff(stringList, Arrays.asList(modifiedString.split(separator))).getDeltas()); - Collections.reverse(deltaList); - for (Delta delta : deltaList) { - int startPos = delta.getOriginal().getPosition(); - List lines = delta.getOriginal().getLines(); - int offset = 0; - switch (delta.getType()) { - case CHANGE: - for (String line : lines) { - stringList.set(startPos + offset, (offset == 0 ? DiffHighlighting.REMOVAL_START : "") + line); - offset++; - } - stringList.set((startPos + offset) - 1, - stringList.get((startPos + offset) - 1) + DiffHighlighting.TAG_END + separator + DiffHighlighting.ADDITION_START - + String.join(separator, delta.getRevised().getLines()) + DiffHighlighting.TAG_END); - break; - case DELETE: - for (String line : lines) { - stringList.set(startPos + offset, (offset == 0 ? DiffHighlighting.REMOVAL_START : "") + line); - offset++; - } - stringList.set((startPos + offset) - 1, - stringList.get((startPos + offset) - 1) + DiffHighlighting.TAG_END); - break; - case INSERT: - stringList.add(delta.getOriginal().getPosition(), - DiffHighlighting.ADDITION_START + String.join(separator, delta.getRevised().getLines()) + DiffHighlighting.TAG_END); - break; - default: - break; - } - } - return String.join(separator, stringList); - } - return modifiedString; - } - - public static String generateSymmetricHighlighting(String baseString, String modifiedString, String separator) { - if ((baseString != null) && (modifiedString != null)) { - List stringList = new ArrayList<>(Arrays.asList(baseString.split(separator))); - List> deltaList = new ArrayList<>(DiffUtils - .diff(stringList, new ArrayList<>(Arrays.asList(modifiedString.split(separator)))).getDeltas()); - Collections.reverse(deltaList); - for (Delta delta : deltaList) { - int startPos = delta.getOriginal().getPosition(); - List lines = delta.getOriginal().getLines(); - int offset = 0; - switch (delta.getType()) { - case CHANGE: - for (String line : lines) { - stringList.set(startPos + offset, (offset == 0 ? DiffHighlighting.CHANGE_START : "") + line); - offset++; - } - stringList.set((startPos + offset) - 1, stringList.get((startPos + offset) - 1) + DiffHighlighting.TAG_END); - break; - case DELETE: - for (String line : lines) { - stringList.set(startPos + offset, (offset == 0 ? DiffHighlighting.ADDITION_START : "") + line); - offset++; - } - stringList.set((startPos + offset) - 1, stringList.get((startPos + offset) - 1) + DiffHighlighting.TAG_END); - break; - case INSERT: - break; - default: - break; - } - } - return String.join(separator, stringList); - } - return modifiedString; - } - -} diff --git a/src/main/java/org/jabref/model/entry/BibtexString.java b/src/main/java/org/jabref/model/entry/BibtexString.java index b03d9ba08e7..52268364cd3 100644 --- a/src/main/java/org/jabref/model/entry/BibtexString.java +++ b/src/main/java/org/jabref/model/entry/BibtexString.java @@ -135,7 +135,7 @@ public boolean hasChanged() { } /* - * Returns user comments (arbitrary text before the string) if there are any. If not returns the empty string + * Returns user comments (arbitrary text before the string) if there are any. If not returns the empty string */ public String getUserComments() { if (parsedSerialization != null) { @@ -173,8 +173,12 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } BibtexString that = (BibtexString) o; return hasChanged == that.hasChanged && Objects.equals(name, that.name) && @@ -188,5 +192,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(name, content, id, type, parsedSerialization, hasChanged); } - } diff --git a/src/main/java/org/jabref/model/entry/identifier/MathSciNetId.java b/src/main/java/org/jabref/model/entry/identifier/MathSciNetId.java index c848e56025b..f07ec8be273 100644 --- a/src/main/java/org/jabref/model/entry/identifier/MathSciNetId.java +++ b/src/main/java/org/jabref/model/entry/identifier/MathSciNetId.java @@ -27,8 +27,12 @@ public static Optional parse(String mrNumberRaw) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } MathSciNetId that = (MathSciNetId) o; return Objects.equals(identifier, that.identifier); } diff --git a/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java index 062648d7f72..f4952a4f751 100644 --- a/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java +++ b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java @@ -42,8 +42,12 @@ public AbstractGroup deepCopy() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } AutomaticKeywordGroup that = (AutomaticKeywordGroup) o; return Objects.equals(keywordDelimiter, that.keywordDelimiter) && Objects.equals(field, that.field); diff --git a/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java b/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java index 61992508ab9..58f11af3b4c 100644 --- a/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java +++ b/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java @@ -21,8 +21,12 @@ public AutomaticPersonsGroup(String name, GroupHierarchyType context, String fie @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } AutomaticPersonsGroup that = (AutomaticPersonsGroup) o; return Objects.equals(field, that.field); } diff --git a/src/main/java/org/jabref/model/groups/TexGroup.java b/src/main/java/org/jabref/model/groups/TexGroup.java index 3c0ef915db9..a9e7f82e006 100644 --- a/src/main/java/org/jabref/model/groups/TexGroup.java +++ b/src/main/java/org/jabref/model/groups/TexGroup.java @@ -59,9 +59,15 @@ public AbstractGroup deepCopy() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } TexGroup group = (TexGroup) o; return Objects.equals(filePath, group.filePath); } diff --git a/src/main/java/org/jabref/model/metadata/ContentSelectors.java b/src/main/java/org/jabref/model/metadata/ContentSelectors.java index 96269ff9349..7f841494bf2 100644 --- a/src/main/java/org/jabref/model/metadata/ContentSelectors.java +++ b/src/main/java/org/jabref/model/metadata/ContentSelectors.java @@ -21,7 +21,7 @@ public void addContentSelector(ContentSelector contentSelector) { } public List getSelectorValuesForField(String fieldName) { - for (ContentSelector selector: contentSelectors) { + for (ContentSelector selector : contentSelectors) { if (selector.getFieldName().equals(fieldName)) { return selector.getValues(); } @@ -33,7 +33,7 @@ public List getSelectorValuesForField(String fieldName) { public void removeSelector(String fieldName) { ContentSelector toRemove = null; - for (ContentSelector selector: contentSelectors) { + for (ContentSelector selector : contentSelectors) { if (selector.getFieldName().equals(fieldName)) { toRemove = selector; break; @@ -61,7 +61,7 @@ public static ContentSelector parse(String key, String values) { public List getFieldNamesWithSelectors() { List result = new ArrayList<>(contentSelectors.size()); - for (ContentSelector selector: contentSelectors) { + for (ContentSelector selector : contentSelectors) { result.add(selector.getFieldName()); } @@ -70,8 +70,12 @@ public List getFieldNamesWithSelectors() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } ContentSelectors that = (ContentSelectors) o; return Objects.equals(contentSelectors, that.contentSelectors); } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index a61c1762f99..cbc7c80163c 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -50,6 +50,7 @@ import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.maintable.ColumnPreferences; import org.jabref.gui.maintable.MainTablePreferences; +import org.jabref.gui.mergeentries.MergeEntries; import org.jabref.gui.preferences.ImportSettingsTab; import org.jabref.gui.util.ThemeLoader; import org.jabref.logic.bibtex.FieldContentParserPreferences; @@ -569,7 +570,7 @@ private JabRefPreferences() { defaults.put(DEFAULT_AUTO_SORT, Boolean.FALSE); - defaults.put(MERGE_ENTRIES_DIFF_MODE, 2); + defaults.put(MERGE_ENTRIES_DIFF_MODE, MergeEntries.DiffMode.WORD.name()); defaults.put(SHOW_RECOMMENDATIONS, Boolean.TRUE); defaults.put(ACCEPT_RECOMMENDATIONS, Boolean.FALSE); @@ -784,7 +785,7 @@ private JabRefPreferences() { + "__NEWLINE__

"); // set default theme - defaults.put(JabRefPreferences.FX_THEME, ThemeLoader.DEFAULT_MAIN_CSS); + defaults.put(JabRefPreferences.FX_THEME, ThemeLoader.MAIN_CSS); setLanguageDependentDefaultValues(); } diff --git a/src/main/java/org/jabref/styletester/StyleTesterMain.java b/src/main/java/org/jabref/styletester/StyleTesterMain.java index 68a7331b989..13fa1ffcf7c 100644 --- a/src/main/java/org/jabref/styletester/StyleTesterMain.java +++ b/src/main/java/org/jabref/styletester/StyleTesterMain.java @@ -30,7 +30,7 @@ public void start(Stage stage) throws JabRefException { JabRefExecutorService.INSTANCE.executeInterruptableTask(fileUpdateMonitor, "FileUpdateMonitor"); Scene scene = new Scene(view.getContent()); - new ThemeLoader(fileUpdateMonitor, JabRefPreferences.getInstance()).installBaseCss(scene, JabRefPreferences.getInstance()); + new ThemeLoader(fileUpdateMonitor, JabRefPreferences.getInstance()).installCss(scene, JabRefPreferences.getInstance()); stage.setScene(scene); stage.show(); } diff --git a/src/main/resources/csl-locales b/src/main/resources/csl-locales new file mode 160000 index 00000000000..29ed2ff4328 --- /dev/null +++ b/src/main/resources/csl-locales @@ -0,0 +1 @@ +Subproject commit 29ed2ff43284f726f9f583981650a86ffb9b236f diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles new file mode 160000 index 00000000000..6ed87f55fd7 --- /dev/null +++ b/src/main/resources/csl-styles @@ -0,0 +1 @@ +Subproject commit 6ed87f55fd75fcecbf8f6b8a96d5edd0c935702e diff --git a/src/main/resources/icons/JabRef-icon-64.png b/src/main/resources/icons/JabRef-icon-64.png new file mode 100644 index 00000000000..e807e0448d3 Binary files /dev/null and b/src/main/resources/icons/JabRef-icon-64.png differ diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index aa3101e4371..d58e0cbc12d 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1490,7 +1490,6 @@ Canceled\ merging\ entries=Canceled merging entries Format\ units\ by\ adding\ non-breaking\ separators\ and\ keeping\ the\ correct\ case\ on\ search=Format units by adding non-breaking separators and keeping the correct case on search Merge\ entries=Merge entries Merged\ entries=Merged entries -Merged\ entry=Merged entry None=None Parse=Parse Result=Result @@ -1574,9 +1573,7 @@ Add\ new\ file\ type=Add new file type Left\ entry=Left entry Right\ entry=Right entry -Use=Use Original\ entry=Original entry -Replace\ original\ entry=Replace original entry No\ information\ added=No information added Select\ at\ least\ one\ entry\ to\ manage\ keywords.=Select at least one entry to manage keywords. OpenDocument\ text=OpenDocument text @@ -1614,7 +1611,6 @@ Print\ entry\ preview=Print entry preview Copy\ title=Copy title Copy\ \\cite{BibTeX\ key}=Copy \\cite{BibTeX key} Copy\ BibTeX\ key\ and\ title=Copy BibTeX key and title -Merged\ BibTeX\ source\ code=Merged BibTeX source code Invalid\ DOI\:\ '%0'.=Invalid DOI: '%0'. should\ start\ with\ a\ name=should start with a name should\ end\ with\ a\ name=should end with a name diff --git a/src/test/java/org/jabref/gui/mergeentries/DiffHighlightingTest.java b/src/test/java/org/jabref/gui/mergeentries/DiffHighlightingTest.java new file mode 100644 index 00000000000..70d0b601cd6 --- /dev/null +++ b/src/test/java/org/jabref/gui/mergeentries/DiffHighlightingTest.java @@ -0,0 +1,174 @@ +package org.jabref.gui.mergeentries; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.scene.text.Text; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testfx.framework.junit5.ApplicationExtension; + +@ExtendWith(ApplicationExtension.class) +class DiffHighlightingTest { + + public static void assertEquals(List expected, List actual) { + // Need to compare string values since Texts with the same string are not considered equal + Assertions.assertEquals(expected.toString(), actual.toString()); + + // Moreover, make sure that style classes are correct + List expectedStyles = expected.stream().map(text -> text.getStyleClass().toString()).collect(Collectors.toList()); + List actualStyles = actual.stream().map(text -> text.getStyleClass().toString()).collect(Collectors.toList()); + Assertions.assertEquals(expectedStyles, actualStyles); + } + + @Test + void testGenerateDiffHighlightingBothNullThrowsNPE() { + Assertions.assertThrows(NullPointerException.class, () -> DiffHighlighting.generateDiffHighlighting(null, null, "")); + } + + @Test + void testNullSeparatorThrowsNPE() { + Assertions.assertThrows(NullPointerException.class, () -> DiffHighlighting.generateDiffHighlighting("", "", null)); + } + + @Test + void testGenerateDiffHighlightingNoDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forUnchanged("f"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forUnchanged("o") + ), + DiffHighlighting.generateDiffHighlighting("foo", "foo", "")); + } + + @Test + void testGenerateDiffHighlightingSingleWordAddTextWordDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forRemoved("foo "), + DiffHighlighting.forAdded("foobar") + ), + DiffHighlighting.generateDiffHighlighting("foo", "foobar", " ")); + } + + @Test + void testGenerateDiffHighlightingSingleWordAddTextCharacterDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forUnchanged("f"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forAdded("bar") + ), + DiffHighlighting.generateDiffHighlighting("foo", "foobar", "")); + } + + @Test + void testGenerateDiffHighlightingSingleWordDeleteTextWordDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forRemoved("foobar "), + DiffHighlighting.forAdded("foo") + ), + DiffHighlighting.generateDiffHighlighting("foobar", "foo", " ")); + } + + @Test + void testGenerateDiffHighlightingSingleWordDeleteTextCharacterDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forUnchanged("f"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forRemoved("b"), + DiffHighlighting.forRemoved("a"), + DiffHighlighting.forRemoved("r") + ), + DiffHighlighting.generateDiffHighlighting("foobar", "foo", "")); + } + + @Test + void generateSymmetricHighlightingSingleWordAddTextWordDiff() { + assertEquals( + Collections.singletonList(DiffHighlighting.forChanged("foo ")), + DiffHighlighting.generateSymmetricHighlighting("foo", "foobar", " ")); + } + + @Test + void generateSymmetricHighlightingSingleWordAddTextCharacterDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forUnchanged("f"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forUnchanged("o") + ), + DiffHighlighting.generateSymmetricHighlighting("foo", "foobar", "")); + } + + @Test + void generateSymmetricHighlightingSingleWordDeleteTextWordDiff() { + assertEquals( + Collections.singletonList(DiffHighlighting.forChanged("foobar ")), + DiffHighlighting.generateSymmetricHighlighting("foobar", "foo", " ")); + } + + @Test + void generateSymmetricHighlightingSingleWordDeleteTextCharacterDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forUnchanged("f"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forAdded("b"), + DiffHighlighting.forAdded("a"), + DiffHighlighting.forAdded("r") + ), + DiffHighlighting.generateSymmetricHighlighting("foobar", "foo", "")); + } + + @Test + void generateSymmetricHighlightingMultipleWordsDeleteTextCharacterDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forUnchanged("f"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forUnchanged("o"), + DiffHighlighting.forAdded("b"), + DiffHighlighting.forAdded("a"), + DiffHighlighting.forAdded("r"), + DiffHighlighting.forUnchanged(" "), + DiffHighlighting.forUnchanged("a"), + DiffHighlighting.forUnchanged("n"), + DiffHighlighting.forUnchanged("d"), + DiffHighlighting.forUnchanged(" "), + DiffHighlighting.forAdded("s"), + DiffHighlighting.forAdded("o"), + DiffHighlighting.forAdded("m"), + DiffHighlighting.forAdded("e"), + DiffHighlighting.forUnchanged("t"), + DiffHighlighting.forUnchanged("h"), + DiffHighlighting.forUnchanged("i"), + DiffHighlighting.forUnchanged("n"), + DiffHighlighting.forUnchanged("g") + ), + DiffHighlighting.generateSymmetricHighlighting("foobar and something", "foo and thing", "")); + } + + @Test + void generateSymmetricHighlightingMultipleWordsDeleteTextWordDiff() { + assertEquals( + Arrays.asList( + DiffHighlighting.forUnchanged("foo "), + DiffHighlighting.forAdded("bar "), + DiffHighlighting.forUnchanged("and "), + DiffHighlighting.forAdded("some "), + DiffHighlighting.forUnchanged("thing ") + ), + DiffHighlighting.generateSymmetricHighlighting("foo bar and some thing", "foo and thing", " ")); + } +} diff --git a/src/test/java/org/jabref/gui/search/TextFlowEqualityHelper.java b/src/test/java/org/jabref/gui/search/TextFlowEqualityHelper.java index 0ac74f99233..15246db4681 100644 --- a/src/test/java/org/jabref/gui/search/TextFlowEqualityHelper.java +++ b/src/test/java/org/jabref/gui/search/TextFlowEqualityHelper.java @@ -28,16 +28,18 @@ public static void assertEquals(List expectedTexts, TextFlow description) } public static boolean checkIfTextsEqualsExpectedTexts(List texts, List expectedTexts) { - if (expectedTexts.size() != texts.size()) + if (expectedTexts.size() != texts.size()) { return false; + } Text expectedText; for (int i = 0; i < expectedTexts.size(); i++) { expectedText = expectedTexts.get(i); // the strings contain not only the text but also the font and other properties // so comparing them compares the Text object as a whole // the equals method is not implemented... - if (!expectedText.toString().equals(texts.get(i).toString())) + if (!expectedText.toString().equals(texts.get(i).toString())) { return false; + } } return true; } diff --git a/src/test/java/org/jabref/logic/importer/fileformat/PdfContentImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/PdfContentImporterTest.java index db7396d047c..b82795865d6 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/PdfContentImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/PdfContentImporterTest.java @@ -1,6 +1,5 @@ package org.jabref.logic.importer.fileformat; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; @@ -10,6 +9,8 @@ import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.util.StandardFileType; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibtexEntryTypes; +import org.jabref.model.entry.FieldName; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,14 +35,30 @@ public void testsGetExtensions() { @Test public void testGetDescription() { assertEquals( - "PdfContentImporter parses data of the first page of the PDF and creates a BibTeX entry. Currently, Springer and IEEE formats are supported.", - importer.getDescription()); + "PdfContentImporter parses data of the first page of the PDF and creates a BibTeX entry. Currently, Springer and IEEE formats are supported.", + importer.getDescription()); } @Test - public void doesNotHandleEncryptedPdfs() throws URISyntaxException { + public void doesNotHandleEncryptedPdfs() throws Exception { Path file = Paths.get(PdfContentImporter.class.getResource("/pdfs/encrypted.pdf").toURI()); List result = importer.importDatabase(file, StandardCharsets.UTF_8).getDatabase().getEntries(); assertEquals(Collections.emptyList(), result); } + + @Test + public void importTwiceWorksAsExpected() throws Exception { + Path file = Paths.get(PdfContentImporter.class.getResource("/pdfs/minimal.pdf").toURI()); + List result = importer.importDatabase(file, StandardCharsets.UTF_8).getDatabase().getEntries(); + + BibEntry expected = new BibEntry(BibtexEntryTypes.INPROCEEDINGS); + expected.setField(FieldName.AUTHOR, "1 "); + expected.setField(FieldName.TITLE, "Hello World"); + + List resultSecondImport = importer.importDatabase(file, StandardCharsets.UTF_8).getDatabase().getEntries(); + assertEquals(Collections.singletonList(expected), result); + assertEquals(Collections.singletonList(expected), resultSecondImport); + + } + } diff --git a/src/test/java/org/jabref/logic/util/strings/DiffHighlightingTest.java b/src/test/java/org/jabref/logic/util/strings/DiffHighlightingTest.java deleted file mode 100644 index bda3faa126c..00000000000 --- a/src/test/java/org/jabref/logic/util/strings/DiffHighlightingTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.jabref.logic.util.strings; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class DiffHighlightingTest { - - @Test - public void testGenerateDiffHighlightingBothNullReturnsNull() { - assertNull(DiffHighlighting.generateDiffHighlighting(null, null, "")); - } - - @Test - public void testNullSeparatorThrowsNPE() { - assertThrows(NullPointerException.class, () -> DiffHighlighting.generateDiffHighlighting("", "", null)); - } - - @Test - public void testGenerateDiffHighlightingNoDiff() { - assertEquals("foo", DiffHighlighting.generateDiffHighlighting("foo", "foo", "")); - } - - @Test - public void testGenerateDiffHighlightingSingleWordAddTextWordDiff() { - assertEquals("foo foobar", - DiffHighlighting.generateDiffHighlighting("foo", "foobar", " ")); - } - - @Test - public void testGenerateDiffHighlightingSingleWordAddTextCharacterDiff() { - assertEquals("foobar", DiffHighlighting.generateDiffHighlighting("foo", "foobar", "")); - } - - @Test - public void testGenerateDiffHighlightingSingleWordDeleteTextWordDiff() { - assertEquals("foobar foo", - DiffHighlighting.generateDiffHighlighting("foobar", "foo", " ")); - } - - @Test - public void testGenerateDiffHighlightingSingleWordDeleteTextCharacterDiff() { - assertEquals("foobar", DiffHighlighting.generateDiffHighlighting("foobar", "foo", "")); - } - - @Test - public void generateSymmetricHighlightingSingleWordAddTextWordDiff() { - assertEquals("foo", - DiffHighlighting.generateSymmetricHighlighting("foo", "foobar", " ")); - } - - @Test - public void generateSymmetricHighlightingSingleWordAddTextCharacterDiff() { - assertEquals("foo", DiffHighlighting.generateSymmetricHighlighting("foo", "foobar", "")); - } - - @Test - public void generateSymmetricHighlightingSingleWordDeleteTextWordDiff() { - assertEquals("foobar", - DiffHighlighting.generateSymmetricHighlighting("foobar", "foo", " ")); - } - - @Test - public void generateSymmetricHighlightingSingleWordDeleteTextCharacterDiff() { - assertEquals("foobar", DiffHighlighting.generateSymmetricHighlighting("foobar", "foo", "")); - } - - @Test - public void generateSymmetricHighlightingMultipleWordsDeleteTextCharacterDiff() { - assertEquals("foobar and something", - DiffHighlighting.generateSymmetricHighlighting("foobar and something", "foo and thing", "")); - } - - @Test - public void generateSymmetricHighlightingMultipleWordsDeleteTextWordDiff() { - assertEquals("foo bar and some thing", - DiffHighlighting.generateSymmetricHighlighting("foo bar and some thing", "foo and thing", " ")); - } -}