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 2531456c6f0..bae1646a300 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/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/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/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/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/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/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 45f724fc252..9371eb35148 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.logic.bibtex.FieldContentParserPreferences; import org.jabref.logic.bibtex.LatexFieldFormatterPreferences; @@ -567,7 +568,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(VALIDATE_IN_ENTRY_EDITOR, Boolean.TRUE); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 529922c4b94..738cfd41c23 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1474,7 +1474,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 @@ -1558,9 +1557,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 @@ -1598,7 +1595,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/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", " ")); - } -}