diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 055eaba332f..932491c4800 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -58,6 +58,8 @@ import org.jabref.gui.maintable.MainTableDataModel; import org.jabref.gui.mergeentries.MergeEntriesAction; import org.jabref.gui.mergeentries.MergeWithFetchedEntryAction; +import org.jabref.gui.preview.CitationStyleToClipboardWorker; +import org.jabref.gui.preview.PreviewPanel; import org.jabref.gui.specialfields.SpecialFieldDatabaseChangeListener; import org.jabref.gui.specialfields.SpecialFieldValueViewModel; import org.jabref.gui.specialfields.SpecialFieldViewModel; @@ -67,7 +69,6 @@ import org.jabref.gui.undo.UndoableInsertEntry; import org.jabref.gui.undo.UndoableRemoveEntry; import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.worker.CitationStyleToClipboardWorker; import org.jabref.gui.worker.SendAsEMailAction; import org.jabref.logic.citationstyle.CitationStyleCache; import org.jabref.logic.citationstyle.CitationStyleOutputFormat; @@ -130,6 +131,7 @@ public class BasePanel extends StackPane { private final ExternalFileTypes externalFileTypes; private final EntryEditor entryEditor; + private final DialogService dialogService; private MainTable mainTable; // To contain instantiated entry editors. This is to save time // As most enums, this must not be null @@ -137,23 +139,17 @@ public class BasePanel extends StackPane { private SplitPane splitPane; private DatabaseChangePane changePane; private boolean saving; - // AutoCompleter used in the search bar private PersonNameSuggestionProvider searchAutoCompleter; private boolean baseChanged; private boolean nonUndoableChange; // Used to track whether the base has changed since last save. private BibEntry showing; - private SuggestionProviders suggestionProviders; - @SuppressWarnings({"FieldCanBeLocal", "unused"}) private Subscription dividerPositionSubscription; - // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); - private Optional changeMonitor = Optional.empty(); - private final DialogService dialogService; public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabaseContext bibDatabaseContext, ExternalFileTypes externalFileTypes) { this.preferences = Objects.requireNonNull(preferences); @@ -188,7 +184,7 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas this.entryEditor = new EntryEditor(this, preferences.getEntryEditorPreferences(), Globals.getFileUpdateMonitor(), dialogService, externalFileTypes, Globals.TASK_EXECUTOR); - this.preview = new PreviewPanel(this, getBibDatabaseContext(), preferences.getKeyBindings(), preferences.getPreviewPreferences(), dialogService, externalFileTypes); + this.preview = new PreviewPanel(getBibDatabaseContext(), this, dialogService, externalFileTypes, Globals.getKeyPrefs(), preferences.getPreviewPreferences()); frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(preview); } @@ -954,7 +950,7 @@ public void entryEditorClosing(EntryEditor editor) { */ public void ensureNotShowingBottomPanel(BibEntry entry) { if (((mode == BasePanelMode.SHOWING_EDITOR) && (entryEditor.getEntry() == entry)) - || ((mode == BasePanelMode.SHOWING_PREVIEW) && (preview.getEntry() == entry))) { + || ((mode == BasePanelMode.SHOWING_PREVIEW))) { closeBottomPane(); } } @@ -1140,10 +1136,6 @@ public CitationStyleCache getCitationStyleCache() { return citationStyleCache; } - public PreviewPanel getPreviewPanel() { - return preview; - } - public FileAnnotationCache getAnnotationCache() { return annotationCache; } @@ -1173,6 +1165,11 @@ public void cut() { mainTable.cut(); } + @Subscribe + public void listen(EntryChangedEvent entryChangedEvent) { + this.markBaseChanged(); + } + private static class SearchAndOpenFile { private final BibEntry entry; @@ -1281,11 +1278,6 @@ public void listen(EntryRemovedEvent removedEntryEvent) { } } - @Subscribe - public void listen(EntryChangedEvent entryChangedEvent) { - this.markBaseChanged(); - } - private class UndoAction implements BaseAction { @Override diff --git a/src/main/java/org/jabref/gui/EntryContainer.java b/src/main/java/org/jabref/gui/EntryContainer.java deleted file mode 100644 index 75535781813..00000000000 --- a/src/main/java/org/jabref/gui/EntryContainer.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.jabref.gui; - -import org.jabref.model.entry.BibEntry; - -/** - * Entry containers work on a single entry, which can be asked for - */ -@FunctionalInterface -public interface EntryContainer { - - BibEntry getEntry(); -} diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 2fff7ba1116..e70d9429b6d 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -232,8 +232,6 @@ public void init() { } globalSearchBar.setSearchTerm(content); - currentBasePanel.getPreviewPanel().updateLayout(Globals.prefs.getPreviewPreferences()); - // groupSidePane.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(GroupSidePane.class)); //previewToggle.setSelected(Globals.prefs.getPreviewPreferences().isPreviewPanelEnabled()); //generalFetcher.getToggleCommand().setSelected(sidePaneManager.isComponentVisible(WebSearchPane.class)); diff --git a/src/main/java/org/jabref/gui/PreviewPanel.java b/src/main/java/org/jabref/gui/PreviewPanel.java deleted file mode 100644 index ff26ee6765b..00000000000 --- a/src/main/java/org/jabref/gui/PreviewPanel.java +++ /dev/null @@ -1,381 +0,0 @@ -package org.jabref.gui; - -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.Future; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import javafx.print.PrinterJob; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.MenuItem; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.input.ClipboardContent; -import javafx.scene.input.DataFormat; -import javafx.scene.input.Dragboard; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.TransferMode; -import javafx.scene.web.WebView; - -import org.jabref.Globals; -import org.jabref.gui.externalfiles.ExternalFilesEntryLinker; -import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.icon.IconTheme; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.keyboard.KeyBindingRepository; -import org.jabref.gui.util.BackgroundTask; -import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.logic.citationstyle.CitationStyle; -import org.jabref.logic.exporter.ExporterFactory; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.layout.Layout; -import org.jabref.logic.layout.LayoutFormatterPreferences; -import org.jabref.logic.layout.LayoutHelper; -import org.jabref.logic.search.SearchQueryHighlightListener; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.event.FieldChangedEvent; -import org.jabref.preferences.PreviewPreferences; - -import com.google.common.eventbus.Subscribe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -/** - * Displays an BibEntry using the given layout format. - */ -public class PreviewPanel extends ScrollPane implements SearchQueryHighlightListener, EntryContainer { - - private static final Logger LOGGER = LoggerFactory.getLogger(PreviewPanel.class); - - private final ClipBoardManager clipBoardManager; - private final DialogService dialogService; - private final KeyBindingRepository keyBindingRepository; - - private final String defaultPreviewStyle = "Preview"; - private String previewStyle; - private CitationStyle citationStyle; - private Optional basePanel = Optional.empty(); - - private boolean fixedLayout; - private Optional layout = Optional.empty(); - /** - * The entry currently shown - */ - private Optional bibEntry = Optional.empty(); - - /** - * If a database is set, the preview will attempt to resolve strings in the previewed entry using that database. - */ - private BibDatabaseContext databaseContext; - private final WebView previewView; - private Optional> citationStyleFuture = Optional.empty(); - - private final ExternalFilesEntryLinker fileLinker; - - /** - * @param panel (may be null) Only set this if the preview is associated to the main window. - * @param databaseContext Used for resolving pdf directories for links. Must not be null, just pass a new empty BibDatabaseContext() - */ - public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, KeyBindingRepository keyBindingRepository, PreviewPreferences preferences, DialogService dialogService, ExternalFileTypes externalFileTypes) { - this.databaseContext = Objects.requireNonNull(databaseContext); - this.basePanel = Optional.ofNullable(panel); - this.dialogService = dialogService; - this.clipBoardManager = Globals.clipboardManager; - this.keyBindingRepository = keyBindingRepository; - - fileLinker = new ExternalFilesEntryLinker(externalFileTypes, Globals.prefs.getFilePreferences(), databaseContext); - - // Set up scroll pane for preview pane - setFitToHeight(true); - setFitToWidth(true); - previewView = new WebView(); - setContent(previewView); - previewView.setContextMenuEnabled(false); - setContextMenu(createPopupMenu()); - - if (this.basePanel.isPresent()) { - // Handler for drag content of preview to different window - // only created for main window (not for windows like the search results dialog) - setOnDragDetected(event -> { - startFullDrag(); - - Dragboard dragboard = startDragAndDrop(TransferMode.COPY); - ClipboardContent content = new ClipboardContent(); - content.putHtml((String) previewView.getEngine().executeScript("window.getSelection().toString()")); - dragboard.setContent(content); - - event.consume(); - }); - } - this.previewView.setOnDragOver(event -> { - if (event.getDragboard().hasFiles()) { - event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); - } - event.consume(); - }); - - this.previewView.setOnDragDropped(event -> { - BibEntry entry = this.getEntry(); - boolean success = false; - if (event.getDragboard().hasContent(DataFormat.FILES)) { - List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); - - if (event.getTransferMode() == TransferMode.MOVE) { - LOGGER.debug("Mode MOVE"); //shift on win or no modifier - fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); - } - if (event.getTransferMode() == TransferMode.LINK) { - LOGGER.debug("Node LINK"); //alt on win - fileLinker.addFilesToEntry(entry, files); - } - if (event.getTransferMode() == TransferMode.COPY) { - LOGGER.debug("Mode Copy"); //ctrl on win, no modifier on Xubuntu - fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); - } - success = true; - } - - event.setDropCompleted(success); - event.consume(); - - }); - - createKeyBindings(); - updateLayout(preferences, true); - } - - private void createKeyBindings() { - addEventFilter(KeyEvent.KEY_PRESSED, event -> { - Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); - if (keyBinding.isPresent()) { - switch (keyBinding.get()) { - case COPY_PREVIEW: - copyPreviewToClipBoard(); - event.consume(); - break; - case CLOSE: - close(); - event.consume(); - break; - default: - } - } - }); - } - - private ContextMenu createPopupMenu() { - MenuItem copyPreview = new MenuItem(Localization.lang("Copy preview"), IconTheme.JabRefIcons.COPY.getGraphicNode()); - copyPreview.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.COPY_PREVIEW)); - copyPreview.setOnAction(event -> copyPreviewToClipBoard()); - MenuItem printEntryPreview = new MenuItem(Localization.lang("Print entry preview"), IconTheme.JabRefIcons.PRINTED.getGraphicNode()); - printEntryPreview.setOnAction(event -> print()); - MenuItem previousPreviewLayout = new MenuItem(Localization.lang("Previous preview layout")); - previousPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.PREVIOUS_PREVIEW_LAYOUT)); - previousPreviewLayout.setOnAction(event -> basePanel.ifPresent(BasePanel::previousPreviewStyle)); - MenuItem nextPreviewLayout = new MenuItem(Localization.lang("Next preview layout")); - nextPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.NEXT_PREVIEW_LAYOUT)); - nextPreviewLayout.setOnAction(event -> basePanel.ifPresent(BasePanel::nextPreviewStyle)); - - ContextMenu menu = new ContextMenu(); - menu.getItems().add(copyPreview); - menu.getItems().add(printEntryPreview); - menu.getItems().add(new SeparatorMenuItem()); - menu.getItems().add(nextPreviewLayout); - menu.getItems().add(previousPreviewLayout); - return menu; - } - - public void setDatabaseContext(BibDatabaseContext databaseContext) { - this.databaseContext = databaseContext; - } - - public Optional getBasePanel() { - return this.basePanel; - } - - public void setBasePanel(BasePanel basePanel) { - this.basePanel = Optional.ofNullable(basePanel); - } - - public void updateLayout(PreviewPreferences previewPreferences) { - updateLayout(previewPreferences, false); - } - - private void updateLayout(PreviewPreferences previewPreferences, boolean init) { - if (fixedLayout) { - LOGGER.debug("cannot change the layout because the layout is fixed"); - return; - } - - String style = previewPreferences.getCurrentPreviewStyle(); - if (previewStyle == null) { - previewStyle = style; - CitationStyle.createCitationStyleFromFile(style).ifPresent(cs -> citationStyle = cs); - } - if (basePanel.isPresent() && !previewStyle.equals(style)) { - if (CitationStyle.isCitationStyleFile(style)) { - layout = Optional.empty(); - CitationStyle.createCitationStyleFromFile(style) - .ifPresent(cs -> { - citationStyle = cs; - if (!init) { - basePanel.get().output(Localization.lang("Preview style changed to: %0", citationStyle.getTitle())); - } - }); - previewStyle = style; - } - } else { - previewStyle = defaultPreviewStyle; - updatePreviewLayout(previewPreferences.getPreviewStyle(), previewPreferences.getLayoutFormatterPreferences()); - if (!init) { - basePanel.get().output(Localization.lang("Preview style changed to: %0", Localization.lang("Preview"))); - } - } - - update(); - } - - private void updatePreviewLayout(String layoutFile, LayoutFormatterPreferences layoutFormatterPreferences) { - StringReader sr = new StringReader(layoutFile.replace("__NEWLINE__", "\n")); - try { - layout = Optional.of(new LayoutHelper(sr, layoutFormatterPreferences).getLayoutFromText()); - } catch (IOException e) { - layout = Optional.empty(); - LOGGER.debug("no layout could be set", e); - } - } - - public void setLayout(Layout layout) { - this.layout = Optional.ofNullable(layout); - update(); - } - - public void setEntry(BibEntry newEntry) { - bibEntry.filter(e -> e != newEntry).ifPresent(e -> e.unregisterListener(this)); - bibEntry = Optional.ofNullable(newEntry); - newEntry.registerListener(this); - - update(); - } - - /** - * Listener for ChangedFieldEvent. - */ - @SuppressWarnings("unused") - @Subscribe - public void listen(FieldChangedEvent fieldChangedEvent) { - update(); - } - - @Override - public BibEntry getEntry() { - return this.bibEntry.orElse(null); - } - - public void update() { - ExporterFactory.entryNumber = 1; // Set entry number in case that is included in the preview layout. - - if (citationStyleFuture.isPresent()) { - citationStyleFuture.get().cancel(true); - citationStyleFuture = Optional.empty(); - } - - if (layout.isPresent()) { - StringBuilder sb = new StringBuilder(); - bibEntry.ifPresent(entry -> sb.append(layout.get() - .doLayout(entry, databaseContext.getDatabase()))); - setPreviewLabel(sb.toString()); - } else if (basePanel.isPresent() && bibEntry.isPresent()) { - if ((citationStyle != null) && !previewStyle.equals(defaultPreviewStyle)) { - basePanel.get().getCitationStyleCache().setCitationStyle(citationStyle); - } - Future citationStyleWorker = BackgroundTask - .wrap(() -> basePanel.get().getCitationStyleCache().getCitationFor(bibEntry.get())) - .onRunning(() -> { - CitationStyle citationStyle = basePanel.get().getCitationStyleCache().getCitationStyle(); - setPreviewLabel("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + - ": " + citationStyle.getTitle() + " ..." + ""); - }) - .onSuccess(this::setPreviewLabel) - .onFailure(exception -> { - LOGGER.error("Error while generating citation style", exception); - setPreviewLabel(Localization.lang("Error while generating citation style")); - }) - .executeWith(Globals.TASK_EXECUTOR); - this.citationStyleFuture = Optional.of(citationStyleWorker); - } - } - - private void setPreviewLabel(String text) { - DefaultTaskExecutor.runInJavaFXThread(() -> { - previewView.getEngine().loadContent(text); - this.setHvalue(0); - }); - } - - @Override - public void highlightPattern(Optional newPattern) { - // TODO: Implement that search phrases are highlighted - update(); - } - - /** - * this fixes the Layout, the user cannot change it anymore. Useful for testing the styles in the settings - * - * @param layout should be either a {@link String} (for the old PreviewStyle) or a {@link CitationStyle}. - */ - public void setFixedLayout(String layout) { - this.fixedLayout = true; - updatePreviewLayout(layout, Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader)); - } - - public void print() { - PrinterJob job = PrinterJob.createPrinterJob(); - boolean proceed = dialogService.showPrintDialog(job); - if (!proceed) { - return; - } - - BackgroundTask.wrap(() -> { - job.getJobSettings().setJobName(bibEntry.flatMap(BibEntry::getCiteKeyOptional).orElse("NO ENTRY")); - previewView.getEngine().print(job); - job.endJob(); - return null; - }) - .onFailure(exception -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), exception)) - .executeWith(Globals.TASK_EXECUTOR); - } - - public void close() { - basePanel.ifPresent(BasePanel::closeBottomPane); - } - - private void copyPreviewToClipBoard() { - StringBuilder previewStringContent = new StringBuilder(); - Document document = previewView.getEngine().getDocument(); - - NodeList nodeList = document.getElementsByTagName("html"); - - //Nodelist does not implement iterable - for (int i = 0; i < nodeList.getLength(); i++) { - Element element = (Element) nodeList.item(i); - previewStringContent.append(element.getTextContent()); - } - - ClipboardContent content = new ClipboardContent(); - content.putString(previewStringContent.toString()); - content.putHtml((String) previewView.getEngine().executeScript("document.documentElement.outerHTML")); - - clipBoardManager.setContent(content); - } -} diff --git a/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java index a84904534bb..4fa50ccca9c 100644 --- a/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java @@ -2,10 +2,8 @@ import javafx.scene.Node; -import org.jabref.Globals; import org.jabref.JabRefGUI; -import org.jabref.gui.PreviewPanel; -import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableInsertEntry; import org.jabref.logic.l10n.Localization; @@ -29,8 +27,8 @@ public void makeChange(BibDatabaseContext database, NamedCompound undoEdit) { @Override public Node description() { - PreviewPanel previewPanel = new PreviewPanel(null, new BibDatabaseContext(), Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), JabRefGUI.getMainFrame().getDialogService(), ExternalFileTypes.getInstance()); - previewPanel.setEntry(diskEntry); - return previewPanel; + PreviewViewer previewViewer = new PreviewViewer(new BibDatabaseContext(), JabRefGUI.getMainFrame().getDialogService()); + previewViewer.setEntry(diskEntry); + return previewViewer; } } diff --git a/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java index 82996fa1e28..f8e2b88a02c 100644 --- a/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java @@ -2,10 +2,8 @@ import javafx.scene.Node; -import org.jabref.Globals; import org.jabref.JabRefGUI; -import org.jabref.gui.PreviewPanel; -import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableRemoveEntry; import org.jabref.logic.bibtex.DuplicateCheck; @@ -46,8 +44,8 @@ public void makeChange(BibDatabaseContext database, NamedCompound undoEdit) { @Override public Node description() { - PreviewPanel previewPanel = new PreviewPanel(null, new BibDatabaseContext(), Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), JabRefGUI.getMainFrame().getDialogService(), ExternalFileTypes.getInstance()); - previewPanel.setEntry(memEntry); - return previewPanel; + PreviewViewer previewViewer = new PreviewViewer(new BibDatabaseContext(), JabRefGUI.getMainFrame().getDialogService()); + previewViewer.setEntry(memEntry); + return previewViewer; } } diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 66b1c5c3765..db587824f42 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -20,7 +20,8 @@ import org.jabref.gui.menus.ChangeEntryTypeMenu; import org.jabref.gui.mergeentries.FetchAndMergeEntry; import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory; -import org.jabref.logic.citationstyle.CitationStyle; +import org.jabref.logic.citationstyle.CitationStylePreviewLayout; +import org.jabref.logic.citationstyle.PreviewLayout; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; import org.jabref.model.entry.specialfields.SpecialField; @@ -127,8 +128,8 @@ private static Menu createCopySubMenu(BasePanel panel, ActionFactory factory, Di // the submenu will behave dependent on what style is currently selected (citation/preview) PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences(); - String style = previewPreferences.getPreviewCycle().get(previewPreferences.getPreviewCyclePosition()); - if (CitationStyle.isCitationStyleFile(style)) { + PreviewLayout style = previewPreferences.getCurrentPreviewStyle(); + if (style instanceof CitationStylePreviewLayout) { copySpecialMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_CITATION_HTML, new OldCommandWrapper(Actions.COPY_CITATION_HTML, panel))); Menu copyCitationMenu = factory.createMenu(StandardActions.COPY_CITATION_MORE); copyCitationMenu.getItems().add(factory.createMenuItem(StandardActions.COPY_CITATION_TEXT, new OldCommandWrapper(Actions.COPY_CITATION_TEXT, panel))); diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java index 7e5e6a89020..cec9e6a9048 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java @@ -12,12 +12,12 @@ import javafx.scene.layout.VBox; import org.jabref.gui.DialogService; -import org.jabref.gui.PreviewPanel; -import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ValueTableCellFactory; import org.jabref.gui.util.ViewModelTableRowFactory; +import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.l10n.Localization; import org.jabref.logic.openoffice.OOBibStyle; import org.jabref.logic.openoffice.StyleLoader; @@ -43,8 +43,8 @@ public class StyleSelectDialogView extends BaseDialog { @Inject private PreferencesService preferencesService; @Inject private DialogService dialogService; private StyleSelectDialogViewModel viewModel; - private PreviewPanel previewArticle; - private PreviewPanel previewBook; + private PreviewViewer previewArticle; + private PreviewViewer previewBook; public StyleSelectDialogView(StyleLoader loader) { @@ -66,14 +66,13 @@ public StyleSelectDialogView(StyleLoader loader) { @FXML private void initialize() { - viewModel = new StyleSelectDialogViewModel(dialogService, loader, preferencesService); - previewArticle = new PreviewPanel(null, new BibDatabaseContext(), preferencesService.getKeyBindingRepository(), preferencesService.getPreviewPreferences(), dialogService, ExternalFileTypes.getInstance()); + previewArticle = new PreviewViewer(new BibDatabaseContext(), dialogService); previewArticle.setEntry(TestEntry.getTestEntry()); vbox.getChildren().add(previewArticle); - previewBook = new PreviewPanel(null, new BibDatabaseContext(), preferencesService.getKeyBindingRepository(), preferencesService.getPreviewPreferences(), dialogService, ExternalFileTypes.getInstance()); + previewBook = new PreviewViewer(new BibDatabaseContext(), dialogService); previewBook.setEntry(TestEntry.getTestEntryBook()); vbox.getChildren().add(previewBook); @@ -89,12 +88,8 @@ private void initialize() { } return null; }) - .withOnMouseClickedEvent(item -> { - return evt -> viewModel.deleteStyle(); - }) - .withTooltip(item -> { - return Localization.lang("Remove style"); - }) + .withOnMouseClickedEvent(item -> evt -> viewModel.deleteStyle()) + .withTooltip(item -> Localization.lang("Remove style")) .install(colDeleteIcon); edit.setOnAction(e -> viewModel.editStyle()); @@ -122,8 +117,8 @@ private void initialize() { EasyBind.subscribe(viewModel.selectedItemProperty(), style -> { tvStyles.getSelectionModel().select(style); - previewArticle.setLayout(style.getStyle().getReferenceFormat("default")); - previewBook.setLayout(style.getStyle().getReferenceFormat("default")); + previewArticle.setLayout(new TextBasedPreviewLayout(style.getStyle().getReferenceFormat("default"))); + previewBook.setLayout(new TextBasedPreviewLayout(style.getStyle().getReferenceFormat("default"))); }); } diff --git a/src/main/java/org/jabref/gui/preferences/PreferencesDialog.java b/src/main/java/org/jabref/gui/preferences/PreferencesDialog.java index 437d26cbdf1..7145caa3ed7 100644 --- a/src/main/java/org/jabref/gui/preferences/PreferencesDialog.java +++ b/src/main/java/org/jabref/gui/preferences/PreferencesDialog.java @@ -24,7 +24,6 @@ import org.jabref.gui.DialogService; import org.jabref.gui.GUIGlobals; import org.jabref.gui.JabRefFrame; -import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.FileDialogConfiguration; @@ -86,7 +85,7 @@ public PreferencesDialog(JabRefFrame parent, TaskExecutor taskExecutor) { preferenceTabs.add(new FileTab(dialogService, prefs)); preferenceTabs.add(new TablePrefsTab(prefs)); preferenceTabs.add(new TableColumnsTab(prefs, frame)); - preferenceTabs.add(new PreviewPrefsTab(dialogService, ExternalFileTypes.getInstance(), taskExecutor)); + preferenceTabs.add(new PreviewPreferencesTab(dialogService, taskExecutor)); preferenceTabs.add(new ExternalTab(frame, this, prefs)); preferenceTabs.add(new GroupsPrefsTab(prefs)); preferenceTabs.add(new EntryEditorPrefsTab(prefs)); diff --git a/src/main/java/org/jabref/gui/preferences/PreviewPrefsTab.java b/src/main/java/org/jabref/gui/preferences/PreviewPreferencesTab.java similarity index 55% rename from src/main/java/org/jabref/gui/preferences/PreviewPrefsTab.java rename to src/main/java/org/jabref/gui/preferences/PreviewPreferencesTab.java index 07dedf198b0..da23285e2e8 100644 --- a/src/main/java/org/jabref/gui/preferences/PreviewPrefsTab.java +++ b/src/main/java/org/jabref/gui/preferences/PreviewPreferencesTab.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Optional; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -24,11 +23,14 @@ import org.jabref.JabRefGUI; import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; -import org.jabref.gui.PreviewPanel; -import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.preview.PreviewViewer; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; +import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.citationstyle.CitationStyle; +import org.jabref.logic.citationstyle.CitationStylePreviewLayout; +import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.TestEntry; import org.jabref.model.database.BibDatabaseContext; @@ -37,39 +39,36 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class PreviewPrefsTab implements PrefsTab { +public class PreviewPreferencesTab implements PrefsTab { - private static final Logger LOGGER = LoggerFactory.getLogger(PreviewPrefsTab.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PreviewPreferencesTab.class); - private final ObservableList availableModel = FXCollections.observableArrayList(); - private final ObservableList chosenModel = FXCollections.observableArrayList(); + private final ObservableList availableModel = FXCollections.observableArrayList(); + private final ObservableList chosenModel = FXCollections.observableArrayList(); - private final ListView available = new ListView<>(availableModel); - private final ListView chosen = new ListView<>(chosenModel); + private final ListView available = new ListView<>(availableModel); + private final ListView chosen = new ListView<>(chosenModel); private final Button btnRight = new Button("»"); private final Button btnLeft = new Button("«"); private final Button btnUp = new Button(Localization.lang("Up")); private final Button btnDown = new Button(Localization.lang("Down")); private final GridPane gridPane = new GridPane(); - private final TextArea layout = new TextArea(); + private final TextArea previewTextArea = new TextArea(); private final Button btnTest = new Button(Localization.lang("Test")); private final Button btnDefault = new Button(Localization.lang("Default")); - private final ScrollPane scrollPane = new ScrollPane(layout); + private final ScrollPane scrollPane = new ScrollPane(previewTextArea); private final DialogService dialogService; - private final ExternalFileTypes externalFileTypes; private final TaskExecutor taskExecutor; - public PreviewPrefsTab(DialogService dialogService, ExternalFileTypes externalFileTypes, TaskExecutor taskExecutor) { + public PreviewPreferencesTab(DialogService dialogService, TaskExecutor taskExecutor) { this.dialogService = dialogService; - this.externalFileTypes = externalFileTypes; this.taskExecutor = taskExecutor; setupLogic(); setupGui(); } private void setupLogic() { - BooleanBinding nothingSelectedFromChosen = Bindings.isEmpty(chosen.getSelectionModel().getSelectedItems()); btnLeft.disableProperty().bind(nothingSelectedFromChosen); @@ -78,19 +77,17 @@ private void setupLogic() { btnRight.disableProperty().bind(Bindings.isEmpty(available.getSelectionModel().getSelectedItems())); btnRight.setOnAction(event -> { - for (Object object : available.getSelectionModel().getSelectedItems()) { - availableModel.remove(object); - chosenModel.add(object); + for (PreviewLayout layout : available.getSelectionModel().getSelectedItems()) { + availableModel.remove(layout); + chosenModel.add(layout); } - storeSettings(); }); btnLeft.setOnAction(event -> { - for (Object object : chosen.getSelectionModel().getSelectedItems()) { - availableModel.add(object); - chosenModel.remove(object); + for (PreviewLayout layout : chosen.getSelectionModel().getSelectedItems()) { + availableModel.add(layout); + chosenModel.remove(layout); } - storeSettings(); }); btnUp.setOnAction(event -> { @@ -101,7 +98,6 @@ private void setupLogic() { chosenModel.add(newIndex, chosenModel.remove(oldIndex)); chosen.getSelectionModel().select(newIndex); } - storeSettings(); }); btnDown.setOnAction(event -> { @@ -114,29 +110,19 @@ private void setupLogic() { chosenModel.add(newIndex, chosenModel.remove(oldIndex)); chosen.getSelectionModel().select(newIndex); } - storeSettings(); }); - btnDefault.setOnAction(event -> layout.setText(Globals.prefs.getPreviewPreferences() - .getPreviewStyleDefault() - .replace("__NEWLINE__", "\n"))); + btnDefault.setOnAction(event -> previewTextArea.setText(Globals.prefs.getPreviewPreferences() + .getDefaultPreviewStyle() + .replace("__NEWLINE__", "\n"))); btnTest.setOnAction(event -> { try { + PreviewViewer testPane = new PreviewViewer(new BibDatabaseContext(), dialogService); + testPane.setEntry(TestEntry.getTestEntry()); - PreviewPanel testPane = new PreviewPanel(null, new BibDatabaseContext(), Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), dialogService, externalFileTypes); - if (chosen.getSelectionModel().getSelectedItems().isEmpty()) { - testPane.setFixedLayout(layout.getText()); - testPane.setEntry(TestEntry.getTestEntry()); - } else { - int indexStyle = chosen.getSelectionModel().getSelectedIndex(); - PreviewPreferences preferences = Globals.prefs.getPreviewPreferences(); - preferences = new PreviewPreferences(preferences.getPreviewCycle(), indexStyle, preferences.getPreviewPanelDividerPosition(), preferences.isPreviewPanelEnabled(), preferences.getPreviewStyle(), preferences.getPreviewStyleDefault()); - - testPane = new PreviewPanel(JabRefGUI.getMainFrame().getCurrentBasePanel(), new BibDatabaseContext(), Globals.getKeyPrefs(), preferences, dialogService, externalFileTypes); - testPane.setEntry(TestEntry.getTestEntry()); - testPane.updateLayout(preferences); - } + PreviewLayout layout = chosen.getSelectionModel().getSelectedItem(); + testPane.setLayout(layout); DialogPane pane = new DialogPane(); pane.setContent(testPane); @@ -166,8 +152,13 @@ private void setupGui() { gridPane.add(chosen, 3, 2); gridPane.add(btnTest, 2, 6); gridPane.add(btnDefault, 3, 6); - layout.setPrefSize(600, 300); + previewTextArea.setPrefSize(600, 300); gridPane.add(scrollPane, 1, 9); + + btnTest.disableProperty().bind(Bindings.isEmpty(chosen.getSelectionModel().getSelectedItems())); + + new ViewModelListCellFactory().withText(PreviewLayout::getName).install(chosen); + new ViewModelListCellFactory().withText(PreviewLayout::getName).install(available); } @Override @@ -180,69 +171,50 @@ public void setValues() { PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences(); chosenModel.clear(); - boolean isPreviewChosen = false; - for (String style : previewPreferences.getPreviewCycle()) { - // in case the style is not a valid citation style file, an empty Optional is returned - Optional citationStyle = CitationStyle.createCitationStyleFromFile(style); - if (citationStyle.isPresent()) { - chosenModel.add(citationStyle.get()); - } else { - if (isPreviewChosen) { - LOGGER.error("Preview is already in the list, something went wrong"); - continue; - } - isPreviewChosen = true; - chosenModel.add(Localization.lang("Preview")); - } - } + chosenModel.addAll(previewPreferences.getPreviewCycle()); availableModel.clear(); - if (!isPreviewChosen) { - availableModel.add(Localization.lang("Preview")); + if (chosenModel.stream().noneMatch(layout -> layout instanceof TextBasedPreviewLayout)) { + availableModel.add(previewPreferences.getTextBasedPreviewLayout()); } - BackgroundTask.wrap(() -> CitationStyle.discoverCitationStyles()) - .onSuccess(value -> { - value.stream() - .filter(style -> !previewPreferences.getPreviewCycle().contains(style.getFilePath())) - .sorted(Comparator.comparing(CitationStyle::getTitle)) - .forEach(availableModel::add); - }) + BackgroundTask.wrap(CitationStyle::discoverCitationStyles) + .onSuccess(value -> value.stream() + .map(CitationStylePreviewLayout::new) + .filter(style -> !chosenModel.contains(style)) + .sorted(Comparator.comparing(PreviewLayout::getName)) + .forEach(availableModel::add)) .onFailure(ex -> { LOGGER.error("something went wrong while adding the discovered CitationStyles to the list ", ex); dialogService.showErrorDialogAndWait(Localization.lang("Error adding discovered CitationStyles"), ex); - }).executeWith(taskExecutor); + }) + .executeWith(taskExecutor); - layout.setText(Globals.prefs.getPreviewPreferences().getPreviewStyle().replace("__NEWLINE__", "\n")); + previewTextArea.setText(previewPreferences.getPreviewStyle().replace("__NEWLINE__", "\n")); } @Override public void storeSettings() { - List styles = new ArrayList<>(); + PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences(); if (chosenModel.isEmpty()) { - chosenModel.add(Localization.lang("Preview")); + chosenModel.add(previewPreferences.getTextBasedPreviewLayout()); } - for (Object obj : chosenModel) { - if (obj instanceof CitationStyle) { - styles.add(((CitationStyle) obj).getFilePath()); - } else if (obj instanceof String) { - styles.add("Preview"); - } - } - PreviewPreferences previewPreferences = Globals.prefs.getPreviewPreferences() - .getBuilder() - .withPreviewCycle(styles) - .withPreviewStyle(layout.getText().replace("\n", "__NEWLINE__")) - .build(); + + PreviewPreferences newPreviewPreferences = Globals.prefs.getPreviewPreferences() + .getBuilder() + .withPreviewCycle(chosenModel) + .withPreviewStyle(previewTextArea.getText().replace("\n", "__NEWLINE__")) + .build(); if (!chosen.getSelectionModel().isEmpty()) { - previewPreferences = previewPreferences.getBuilder().withPreviewCyclePosition(chosen.getSelectionModel().getSelectedIndex()).build(); + newPreviewPreferences = newPreviewPreferences.getBuilder().withPreviewCyclePosition(chosen.getSelectionModel().getSelectedIndex()).build(); } - Globals.prefs.storePreviewPreferences(previewPreferences); + Globals.prefs.storePreviewPreferences(newPreviewPreferences); - // update preview for (BasePanel basePanel : JabRefGUI.getMainFrame().getBasePanelList()) { - basePanel.getPreviewPanel().updateLayout(Globals.prefs.getPreviewPreferences()); + // TODO: Find a better way to update preview + basePanel.closeBottomPane(); + //basePanel.getPreviewPanel().updateLayout(Globals.prefs.getPreviewPreferences()); } } diff --git a/src/main/java/org/jabref/gui/worker/CitationStyleToClipboardWorker.java b/src/main/java/org/jabref/gui/preview/CitationStyleToClipboardWorker.java similarity index 92% rename from src/main/java/org/jabref/gui/worker/CitationStyleToClipboardWorker.java rename to src/main/java/org/jabref/gui/preview/CitationStyleToClipboardWorker.java index 21b272eaff2..80299068046 100644 --- a/src/main/java/org/jabref/gui/worker/CitationStyleToClipboardWorker.java +++ b/src/main/java/org/jabref/gui/preview/CitationStyleToClipboardWorker.java @@ -1,4 +1,4 @@ -package org.jabref.gui.worker; +package org.jabref.gui.preview; import java.io.IOException; import java.io.StringReader; @@ -13,9 +13,10 @@ import org.jabref.gui.DialogService; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; -import org.jabref.logic.citationstyle.CitationStyle; import org.jabref.logic.citationstyle.CitationStyleGenerator; import org.jabref.logic.citationstyle.CitationStyleOutputFormat; +import org.jabref.logic.citationstyle.CitationStylePreviewLayout; +import org.jabref.logic.citationstyle.PreviewLayout; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutFormatterPreferences; @@ -37,7 +38,7 @@ public class CitationStyleToClipboardWorker { private final BasePanel basePanel; private final List selectedEntries; - private final String style; + private final PreviewLayout style; private final String previewStyle; private final CitationStyleOutputFormat outputFormat; private final DialogService dialogService; @@ -46,7 +47,7 @@ public class CitationStyleToClipboardWorker { public CitationStyleToClipboardWorker(BasePanel basePanel, CitationStyleOutputFormat outputFormat, DialogService dialogService, ClipBoardManager clipBoardManager, PreviewPreferences previewPreferences) { this.basePanel = basePanel; this.selectedEntries = basePanel.getSelectedEntries(); - this.style = previewPreferences.getPreviewCycle().get(previewPreferences.getPreviewCyclePosition()); + this.style = previewPreferences.getCurrentPreviewStyle(); this.previewStyle = previewPreferences.getPreviewStyle(); this.outputFormat = outputFormat; this.clipBoardManager = clipBoardManager; @@ -64,11 +65,8 @@ private List generateCitations() throws IOException { // This worker stored the style as filename. The CSLAdapter and the CitationStyleCache store the source of the // style. Therefore, we extract the style source from the file. String styleSource = null; - if (CitationStyle.isCitationStyleFile(style)) { - styleSource = CitationStyle.createCitationStyleFromFile(style) - .filter(citationStyleFromFile -> !citationStyleFromFile.getSource().isEmpty()) - .map(CitationStyle::getSource) - .orElse(null); + if (style instanceof CitationStylePreviewLayout) { + styleSource = ((CitationStylePreviewLayout) style).getSource(); } if (styleSource != null) { return CitationStyleGenerator.generateCitations(selectedEntries, styleSource, outputFormat); @@ -163,7 +161,7 @@ protected static ClipboardContent processHtml(List citations) { private void setClipBoardContent(List citations) { // if it's not a citation style take care of the preview - if (!CitationStyle.isCitationStyleFile(style)) { + if (!(style instanceof CitationStylePreviewLayout)) { clipBoardManager.setHtmlContent(processPreview(citations)); } else { // if it's generated by a citation style take care of each output format diff --git a/src/main/java/org/jabref/gui/preview/PreviewPanel.java b/src/main/java/org/jabref/gui/preview/PreviewPanel.java new file mode 100644 index 00000000000..08db01a8a88 --- /dev/null +++ b/src/main/java/org/jabref/gui/preview/PreviewPanel.java @@ -0,0 +1,176 @@ +package org.jabref.gui.preview; + +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DataFormat; +import javafx.scene.input.Dragboard; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.VBox; + +import org.jabref.Globals; +import org.jabref.gui.BasePanel; +import org.jabref.gui.DialogService; +import org.jabref.gui.externalfiles.ExternalFilesEntryLinker; +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.keyboard.KeyBinding; +import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.search.SearchQueryHighlightListener; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.preferences.PreviewPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PreviewPanel extends VBox implements SearchQueryHighlightListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(PreviewPanel.class); + + private final ExternalFilesEntryLinker fileLinker; + private final KeyBindingRepository keyBindingRepository; + private final PreviewViewer previewView; + private BibEntry entry; + private BasePanel basePanel; + private DialogService dialogService; + + public PreviewPanel(BibDatabaseContext database, BasePanel basePanel, DialogService dialogService, ExternalFileTypes externalFileTypes, KeyBindingRepository keyBindingRepository, PreviewPreferences preferences) { + this.basePanel = basePanel; + this.keyBindingRepository = keyBindingRepository; + this.dialogService = dialogService; + fileLinker = new ExternalFilesEntryLinker(externalFileTypes, Globals.prefs.getFilePreferences(), database); + + previewView = new PreviewViewer(database, dialogService); + previewView.setLayout(preferences.getCurrentPreviewStyle()); + previewView.setContextMenu(createPopupMenu()); + previewView.setOnDragDetected(event -> { + previewView.startFullDrag(); + + Dragboard dragboard = previewView.startDragAndDrop(TransferMode.COPY); + ClipboardContent content = new ClipboardContent(); + content.putHtml(previewView.getSelectionHtmlContent()); + dragboard.setContent(content); + + event.consume(); + }); + + previewView.setOnDragOver(event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); + } + event.consume(); + }); + + previewView.setOnDragDropped(event -> { + boolean success = false; + if (event.getDragboard().hasContent(DataFormat.FILES)) { + List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); + + if (event.getTransferMode() == TransferMode.MOVE) { + LOGGER.debug("Mode MOVE"); //shift on win or no modifier + fileLinker.moveFilesToFileDirAndAddToEntry(entry, files); + } + if (event.getTransferMode() == TransferMode.LINK) { + LOGGER.debug("Node LINK"); //alt on win + fileLinker.addFilesToEntry(entry, files); + } + if (event.getTransferMode() == TransferMode.COPY) { + LOGGER.debug("Mode Copy"); //ctrl on win, no modifier on Xubuntu + fileLinker.copyFilesToFileDirAndAddToEntry(entry, files); + } + success = true; + } + + event.setDropCompleted(success); + event.consume(); + + }); + this.getChildren().add(previewView); + + createKeyBindings(); + updateLayout(preferences, true); + } + + public void close() { + basePanel.closeBottomPane(); + } + + @Override + public void highlightPattern(Optional newPattern) { + // TODO: Implement that search phrases are highlighted + } + + public void updateLayout(PreviewPreferences previewPreferences) { + updateLayout(previewPreferences, false); + } + + private void updateLayout(PreviewPreferences previewPreferences, boolean init) { + PreviewLayout currentPreviewStyle = previewPreferences.getCurrentPreviewStyle(); + previewView.setLayout(currentPreviewStyle); + if (!init) { + dialogService.notify(Localization.lang("Preview style changed to: %0", currentPreviewStyle.getName())); + } + } + + private void createKeyBindings() { + previewView.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + Optional keyBinding = keyBindingRepository.mapToKeyBinding(event); + if (keyBinding.isPresent()) { + switch (keyBinding.get()) { + case COPY_PREVIEW: + previewView.copyPreviewToClipBoard(); + event.consume(); + break; + case CLOSE: + close(); + event.consume(); + break; + default: + } + } + }); + } + + private ContextMenu createPopupMenu() { + MenuItem copyPreview = new MenuItem(Localization.lang("Copy preview"), IconTheme.JabRefIcons.COPY.getGraphicNode()); + copyPreview.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.COPY_PREVIEW)); + copyPreview.setOnAction(event -> previewView.copyPreviewToClipBoard()); + MenuItem printEntryPreview = new MenuItem(Localization.lang("Print entry preview"), IconTheme.JabRefIcons.PRINTED.getGraphicNode()); + printEntryPreview.setOnAction(event -> previewView.print()); + MenuItem previousPreviewLayout = new MenuItem(Localization.lang("Previous preview layout")); + previousPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.PREVIOUS_PREVIEW_LAYOUT)); + previousPreviewLayout.setOnAction(event -> basePanel.previousPreviewStyle()); + MenuItem nextPreviewLayout = new MenuItem(Localization.lang("Next preview layout")); + nextPreviewLayout.setAccelerator(keyBindingRepository.getKeyCombination(KeyBinding.NEXT_PREVIEW_LAYOUT)); + nextPreviewLayout.setOnAction(event -> basePanel.nextPreviewStyle()); + + ContextMenu menu = new ContextMenu(); + menu.getItems().add(copyPreview); + menu.getItems().add(printEntryPreview); + menu.getItems().add(new SeparatorMenuItem()); + menu.getItems().add(nextPreviewLayout); + menu.getItems().add(previousPreviewLayout); + return menu; + } + + public void setEntry(BibEntry entry) { + this.entry = entry; + previewView.setEntry(entry); + } + + public void print() { + previewView.print(); + } +} diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java new file mode 100644 index 00000000000..6432ef4cc3b --- /dev/null +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -0,0 +1,155 @@ +package org.jabref.gui.preview; + +import java.util.Objects; +import java.util.Optional; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.print.PrinterJob; +import javafx.scene.control.ScrollPane; +import javafx.scene.input.ClipboardContent; +import javafx.scene.web.WebView; + +import org.jabref.Globals; +import org.jabref.gui.ClipBoardManager; +import org.jabref.gui.DialogService; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.exporter.ExporterFactory; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * Displays an BibEntry using the given layout format. + */ +public class PreviewViewer extends ScrollPane implements InvalidationListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(PreviewViewer.class); + + private final ClipBoardManager clipBoardManager; + private final DialogService dialogService; + + private final TaskExecutor taskExecutor = Globals.TASK_EXECUTOR; + private final WebView previewView; + private PreviewLayout layout; + /** + * The entry currently shown + */ + private Optional entry = Optional.empty(); + private BibDatabaseContext database; + + /** + * @param database Used for resolving strings and pdf directories for links. + */ + public PreviewViewer(BibDatabaseContext database, DialogService dialogService) { + this.database = Objects.requireNonNull(database); + this.dialogService = dialogService; + this.clipBoardManager = Globals.clipboardManager; + + setFitToHeight(true); + setFitToWidth(true); + previewView = new WebView(); + setContent(previewView); + previewView.setContextMenuEnabled(false); + } + + public void setLayout(PreviewLayout newLayout) { + layout = newLayout; + update(); + } + + public void setEntry(BibEntry newEntry) { + // Remove update listener for old entry + entry.ifPresent(oldEntry -> { + for (Observable observable : oldEntry.getObservables()) { + observable.removeListener(this); + } + }); + + entry = Optional.of(newEntry); + + // Register for changes + for (Observable observable : newEntry.getObservables()) { + observable.addListener(this); + } + + update(); + } + + private void update() { + if (!entry.isPresent() || layout == null) { + // Nothing to do + return; + } + + ExporterFactory.entryNumber = 1; // Set entry number in case that is included in the preview layout. + + BackgroundTask + .wrap(() -> layout.generatePreview(entry.get(), database.getDatabase())) + .onRunning(() -> setPreviewText("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + ": " + layout.getName() + " ..." + "")) + .onSuccess(this::setPreviewText) + .onFailure(exception -> { + LOGGER.error("Error while generating citation style", exception); + setPreviewText(Localization.lang("Error while generating citation style")); + }) + .executeWith(taskExecutor); + } + + private void setPreviewText(String text) { + previewView.getEngine().loadContent(text); + this.setHvalue(0); + } + + public void print() { + PrinterJob job = PrinterJob.createPrinterJob(); + boolean proceed = dialogService.showPrintDialog(job); + if (!proceed) { + return; + } + + BackgroundTask + .wrap(() -> { + job.getJobSettings().setJobName(entry.flatMap(BibEntry::getCiteKeyOptional).orElse("NO ENTRY")); + previewView.getEngine().print(job); + job.endJob(); + }) + .onFailure(exception -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), exception)) + .executeWith(taskExecutor); + } + + public void copyPreviewToClipBoard() { + StringBuilder previewStringContent = new StringBuilder(); + Document document = previewView.getEngine().getDocument(); + + NodeList nodeList = document.getElementsByTagName("html"); + + //Nodelist does not implement iterable + for (int i = 0; i < nodeList.getLength(); i++) { + Element element = (Element) nodeList.item(i); + previewStringContent.append(element.getTextContent()); + } + + ClipboardContent content = new ClipboardContent(); + content.putString(previewStringContent.toString()); + content.putHtml((String) previewView.getEngine().executeScript("document.documentElement.outerHTML")); + + clipBoardManager.setContent(content); + } + + @Override + public void invalidated(Observable observable) { + update(); + } + + public String getSelectionHtmlContent() { + return (String) previewView.getEngine().executeScript("window.getSelection().toString()"); + } +} diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java b/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java index 6bd523cd082..a3d7ab6a62f 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStyleCache.java @@ -2,6 +2,8 @@ import java.util.Objects; +import javax.annotation.ParametersAreNonnullByDefault; + import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.event.EntryRemovedEvent; import org.jabref.model.entry.BibEntry; @@ -20,33 +22,32 @@ public class CitationStyleCache { private static final int CACHE_SIZE = 1024; - private CitationStyle citationStyle; + private PreviewLayout citationStyle; private final LoadingCache citationStyleCache; - - public CitationStyleCache(BibDatabaseContext bibDatabaseContext) { - this(bibDatabaseContext, CitationStyle.getDefault()); - } - - public CitationStyleCache(BibDatabaseContext bibDatabaseContext, CitationStyle citationStyle) { - this.citationStyle = Objects.requireNonNull(citationStyle); + public CitationStyleCache(BibDatabaseContext database) { citationStyleCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader() { @Override + @ParametersAreNonnullByDefault public String load(BibEntry entry) { - return CitationStyleGenerator.generateCitation(entry, getCitationStyle().getSource(), CitationStyleOutputFormat.HTML); + if (citationStyle != null) { + return citationStyle.generatePreview(entry, database.getDatabase()); + } else { + return ""; + } } }); - bibDatabaseContext.getDatabase().registerListener(new BibDatabaseEntryListener()); + database.getDatabase().registerListener(new BibDatabaseEntryListener()); } /** - * returns the citation for the given BibEntry and the set CitationStyle + * Returns the citation for the given entry. */ public String getCitationFor(BibEntry entry) { return citationStyleCache.getUnchecked(entry); } - public void setCitationStyle(CitationStyle citationStyle) { + public void setCitationStyle(PreviewLayout citationStyle) { Objects.requireNonNull(citationStyle); if (!this.citationStyle.equals(citationStyle)) { this.citationStyle = citationStyle; @@ -54,10 +55,6 @@ public void setCitationStyle(CitationStyle citationStyle) { } } - public CitationStyle getCitationStyle() { - return this.citationStyle; - } - private class BibDatabaseEntryListener { /** * removes the outdated citation of the changed entry diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java b/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java index fa23ab8ba71..28a2e6f867e 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java @@ -43,7 +43,7 @@ protected static String generateCitation(BibEntry entry, String style) { * Generates a Citation based on the given entry, style, and output format * @implNote the citation is generated using JavaScript which may take some time, better call it from outside the main Thread */ - protected static String generateCitation(BibEntry entry, String style, CitationStyleOutputFormat outputFormat) { + public static String generateCitation(BibEntry entry, String style, CitationStyleOutputFormat outputFormat) { return generateCitations(Collections.singletonList(entry), style, outputFormat).stream().findFirst().orElse(""); } @@ -63,13 +63,11 @@ public static List generateCitations(List bibEntries, String s } catch (TokenMgrException e) { LOGGER.error("Bad character inside BibEntry", e); // sadly one cannot easily retrieve the bad char from the TokenMgrError - return Collections.singletonList(new StringBuilder() - .append(Localization.lang("Cannot generate preview based on selected citation style.")) - .append(outputFormat.getLineSeparator()) - .append(Localization.lang("Bad character inside entry")) - .append(outputFormat.getLineSeparator()) - .append(e.getLocalizedMessage()) - .toString()); + return Collections.singletonList(Localization.lang("Cannot generate preview based on selected citation style.") + + outputFormat.getLineSeparator() + + Localization.lang("Bad character inside entry") + + outputFormat.getLineSeparator() + + e.getLocalizedMessage()); } } } diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java new file mode 100644 index 00000000000..c07bce6ebfd --- /dev/null +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java @@ -0,0 +1,30 @@ +package org.jabref.logic.citationstyle; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +public class CitationStylePreviewLayout implements PreviewLayout { + private CitationStyle citationStyle; + + public CitationStylePreviewLayout(CitationStyle citationStyle) { + this.citationStyle = citationStyle; + } + + @Override + public String generatePreview(BibEntry entry, BibDatabase database) { + return CitationStyleGenerator.generateCitation(entry, citationStyle.getSource(), CitationStyleOutputFormat.HTML); + } + + @Override + public String getName() { + return citationStyle.getTitle(); + } + + public String getSource() { + return citationStyle.getSource(); + } + + public String getFilePath() { + return citationStyle.getFilePath(); + } +} diff --git a/src/main/java/org/jabref/logic/citationstyle/PreviewLayout.java b/src/main/java/org/jabref/logic/citationstyle/PreviewLayout.java new file mode 100644 index 00000000000..e47eec9f01c --- /dev/null +++ b/src/main/java/org/jabref/logic/citationstyle/PreviewLayout.java @@ -0,0 +1,11 @@ +package org.jabref.logic.citationstyle; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +public interface PreviewLayout { + + String generatePreview(BibEntry entry, BibDatabase database); + + String getName(); +} diff --git a/src/main/java/org/jabref/logic/citationstyle/TextBasedPreviewLayout.java b/src/main/java/org/jabref/logic/citationstyle/TextBasedPreviewLayout.java new file mode 100644 index 00000000000..dced3da5b89 --- /dev/null +++ b/src/main/java/org/jabref/logic/citationstyle/TextBasedPreviewLayout.java @@ -0,0 +1,47 @@ +package org.jabref.logic.citationstyle; + +import java.io.IOException; +import java.io.StringReader; + +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.layout.Layout; +import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.logic.layout.LayoutHelper; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TextBasedPreviewLayout implements PreviewLayout { + private static final Logger LOGGER = LoggerFactory.getLogger(TextBasedPreviewLayout.class); + + private Layout layout; + + public TextBasedPreviewLayout(String layoutText, LayoutFormatterPreferences layoutFormatterPreferences) { + StringReader sr = new StringReader(layoutText.replace("__NEWLINE__", "\n")); + try { + layout = new LayoutHelper(sr, layoutFormatterPreferences).getLayoutFromText(); + } catch (IOException e) { + LOGGER.error("Could not generate layout", e); + } + } + + public TextBasedPreviewLayout(Layout layout) { + this.layout = layout; + } + + @Override + public String generatePreview(BibEntry entry, BibDatabase database) { + if (layout != null) { + return layout.doLayout(entry, database); + } else { + return ""; + } + } + + @Override + public String getName() { + return Localization.lang("Preview"); + } +} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index f977a0262cb..6e41ae34352 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -58,6 +58,9 @@ import org.jabref.logic.bibtex.LatexFieldFormatterPreferences; import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternPreferences; import org.jabref.logic.citationstyle.CitationStyle; +import org.jabref.logic.citationstyle.CitationStylePreviewLayout; +import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.cleanup.CleanupPreferences; import org.jabref.logic.cleanup.CleanupPreset; import org.jabref.logic.cleanup.Cleanups; @@ -1586,7 +1589,13 @@ public VersionPreferences getVersionPreferences() { public JabRefPreferences storePreviewPreferences(PreviewPreferences previewPreferences) { putInt(CYCLE_PREVIEW_POS, previewPreferences.getPreviewCyclePosition()); - putStringList(CYCLE_PREVIEW, previewPreferences.getPreviewCycle()); + putStringList(CYCLE_PREVIEW, previewPreferences.getPreviewCycle().stream().map(layout -> { + if (layout instanceof CitationStylePreviewLayout) { + return ((CitationStylePreviewLayout) layout).getFilePath(); + } else { + return layout.getName(); + } + }).collect(Collectors.toList())); putDouble(PREVIEW_PANEL_HEIGHT, previewPreferences.getPreviewPanelDividerPosition().doubleValue()); put(PREVIEW_STYLE, previewPreferences.getPreviewStyle()); putBoolean(PREVIEW_ENABLED, previewPreferences.isPreviewPanelEnabled()); @@ -1601,7 +1610,26 @@ public PreviewPreferences getPreviewPreferences() { String style = get(PREVIEW_STYLE); String styleDefault = (String) defaults.get(PREVIEW_STYLE); boolean enabled = getBoolean(PREVIEW_ENABLED); - return new PreviewPreferences(cycle, cyclePos, panelHeight, enabled, style, styleDefault); + + // For backwards compatibility always add at least the default preview to the cycle + if (cycle.isEmpty()) { + cycle.add("Preview"); + } + + List layouts = cycle.stream() + .map(layout -> { + if (CitationStyle.isCitationStyleFile(layout)) { + return CitationStyle.createCitationStyleFromFile(layout) + .map(file -> (PreviewLayout) new CitationStylePreviewLayout(file)) + .orElse(null); + } else { + return new TextBasedPreviewLayout(style, getLayoutFormatterPreferences(Globals.journalAbbreviationLoader)); + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + return new PreviewPreferences(layouts, cyclePos, panelHeight, enabled, style, styleDefault); } public void storeProxyPreferences(ProxyPreferences proxyPreferences) { diff --git a/src/main/java/org/jabref/preferences/PreviewPreferences.java b/src/main/java/org/jabref/preferences/PreviewPreferences.java index c8d879e1a70..f723652b0ee 100644 --- a/src/main/java/org/jabref/preferences/PreviewPreferences.java +++ b/src/main/java/org/jabref/preferences/PreviewPreferences.java @@ -3,27 +3,29 @@ import java.util.List; import org.jabref.Globals; +import org.jabref.logic.citationstyle.PreviewLayout; +import org.jabref.logic.citationstyle.TextBasedPreviewLayout; import org.jabref.logic.layout.LayoutFormatterPreferences; public class PreviewPreferences { - private final List previewCycle; + private final List previewCycle; private final int previewCyclePosition; private final Number previewPanelDividerPosition; private final boolean previewPanelEnabled; private final String previewStyle; private final String previewStyleDefault; - public PreviewPreferences(List previewCycle, int previeCyclePosition, Number previewPanelDividerPosition, boolean previewPanelEnabled, String previewStyle, String previewStyleDefault) { + public PreviewPreferences(List previewCycle, int previewCyclePosition, Number previewPanelDividerPosition, boolean previewPanelEnabled, String previewStyle, String previewStyleDefault) { this.previewCycle = previewCycle; - this.previewCyclePosition = previeCyclePosition; + this.previewCyclePosition = previewCyclePosition; this.previewPanelDividerPosition = previewPanelDividerPosition; this.previewPanelEnabled = previewPanelEnabled; this.previewStyle = previewStyle; this.previewStyleDefault = previewStyleDefault; } - public List getPreviewCycle() { + public List getPreviewCycle() { return previewCycle; } @@ -43,7 +45,7 @@ public String getPreviewStyle() { return previewStyle; } - public String getPreviewStyleDefault() { + public String getDefaultPreviewStyle() { return previewStyleDefault; } @@ -51,7 +53,7 @@ public Builder getBuilder() { return new Builder(this); } - public String getCurrentPreviewStyle() { + public PreviewLayout getCurrentPreviewStyle() { return getPreviewCycle().get(getPreviewCyclePosition()); } @@ -59,9 +61,13 @@ public LayoutFormatterPreferences getLayoutFormatterPreferences() { return Globals.prefs.getLayoutFormatterPreferences(Globals.journalAbbreviationLoader); } + public PreviewLayout getTextBasedPreviewLayout() { + return new TextBasedPreviewLayout(getPreviewStyle(), getLayoutFormatterPreferences()); + } + public static class Builder { - private List previewCycle; + private List previewCycle; private int previeCyclePosition; private Number previewPanelDividerPosition; private boolean previewPanelEnabled; @@ -74,10 +80,10 @@ public Builder(PreviewPreferences previewPreferences) { this.previewPanelDividerPosition = previewPreferences.getPreviewPanelDividerPosition(); this.previewPanelEnabled = previewPreferences.isPreviewPanelEnabled(); this.previewStyle = previewPreferences.getPreviewStyle(); - this.previewStyleDefault = previewPreferences.getPreviewStyleDefault(); + this.previewStyleDefault = previewPreferences.getDefaultPreviewStyle(); } - public Builder withPreviewCycle(List previewCycle) { + public Builder withPreviewCycle(List previewCycle) { this.previewCycle = previewCycle; return withPreviewCyclePosition(previeCyclePosition); } diff --git a/src/test/java/org/jabref/gui/worker/CitationStyleToClipboardWorkerTest.java b/src/test/java/org/jabref/gui/preview/CitationStyleToClipboardWorkerTest.java similarity index 99% rename from src/test/java/org/jabref/gui/worker/CitationStyleToClipboardWorkerTest.java rename to src/test/java/org/jabref/gui/preview/CitationStyleToClipboardWorkerTest.java index 02e1121f570..0ac88a6d323 100644 --- a/src/test/java/org/jabref/gui/worker/CitationStyleToClipboardWorkerTest.java +++ b/src/test/java/org/jabref/gui/preview/CitationStyleToClipboardWorkerTest.java @@ -1,4 +1,4 @@ -package org.jabref.gui.worker; +package org.jabref.gui.preview; import java.util.Arrays;