Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable drag'n'drop from maintable to external application #11846

Merged
merged 5 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We improved [cleanup](https://docs.jabref.org/finding-sorting-and-cleaning-entries/cleanupentries) of `arXiv` IDs in distributed in the fields `note`, `version`, `institution`, and `eid` fields. [#11306](https://github.com/JabRef/jabref/issues/11306)
- We added a switch not to store the linked file URL, because it caused troubles at other apps. [#11735](https://github.com/JabRef/jabref/pull/11735)
- When starting a new SLR, the selected catalogs now persist within and across JabRef sessions. [koppor#614](https://github.com/koppor/jabref/issues/614)
- We added support for drag'n'drop on an entry in the maintable to an external application to get the entry preview dropped. [#11846](https://github.com/JabRef/jabref/pull/11846)
- We added a different background color to the search bar to indicate when the search syntax is wrong. [#11658](https://github.com/JabRef/jabref/pull/11658)
- We added a setting which always adds the literal "Cited on pages" text before each JStyle citation. [#11691](https://github.com/JabRef/jabref/pull/11732)

Expand Down
20 changes: 16 additions & 4 deletions src/main/java/org/jabref/gui/maintable/MainTable.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jabref.gui.maintable;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -39,12 +40,14 @@
import org.jabref.gui.maintable.columns.LibraryColumn;
import org.jabref.gui.maintable.columns.MainTableColumn;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.preview.ClipboardContentGenerator;
import org.jabref.gui.search.MatchCategory;
import org.jabref.gui.util.ControlHelper;
import org.jabref.gui.util.CustomLocalDragboard;
import org.jabref.gui.util.UiTaskExecutor;
import org.jabref.gui.util.ViewModelTableRowFactory;
import org.jabref.logic.FilePreferences;
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.database.BibDatabaseContext;
Expand Down Expand Up @@ -75,6 +78,8 @@ public class MainTable extends TableView<BibEntryTableViewModel> {
private final UndoManager undoManager;
private final FilePreferences filePreferences;
private final ImportHandler importHandler;
private final ClipboardContentGenerator clipboardContentGenerator;

private long lastKeyPressTime;
private String columnSearchTerm;

Expand All @@ -100,6 +105,7 @@ public MainTable(MainTableDataModel model,
this.undoManager = libraryTab.getUndoManager();
this.filePreferences = preferences.getFilePreferences();
this.importHandler = importHandler;
this.clipboardContentGenerator = new ClipboardContentGenerator(preferences.getPreviewPreferences(), preferences.getLayoutFormatterPreferences(), Injector.instantiateModelOrService(JournalAbbreviationRepository.class));

MainTablePreferences mainTablePreferences = preferences.getMainTablePreferences();

Expand Down Expand Up @@ -392,12 +398,18 @@ private void handleOnDragDetected(TableRow<BibEntryTableViewModel> row, BibEntry

List<BibEntry> entries = getSelectionModel().getSelectedItems().stream().map(BibEntryTableViewModel::getEntry).collect(Collectors.toList());

// The following is necessary to initiate the drag and drop in JavaFX,
// although we don't need the contents, it does not work without
ClipboardContent content;
try {
content = clipboardContentGenerator.generate(entries, CitationStyleOutputFormat.HTML, database);
} catch (IOException e) {
LOGGER.warn("Could not generate clipboard content. Falling back to empty clipboard", e);
content = new ClipboardContent();
}
// Required to be able to drop the entries inside JabRef
content.put(DragAndDropDataFormats.ENTRIES, "");

// Drag'n'drop to other tabs use COPY TransferMode, drop to group sidepane use MOVE
ClipboardContent content = new ClipboardContent();
Dragboard dragboard = startDragAndDrop(TransferMode.COPY_OR_MOVE);
content.put(DragAndDropDataFormats.ENTRIES, "");
dragboard.setContent(content);

if (!entries.isEmpty()) {
Expand Down
136 changes: 136 additions & 0 deletions src/main/java/org/jabref/gui/preview/ClipboardContentGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package org.jabref.gui.preview;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javafx.scene.input.ClipboardContent;

import org.jabref.logic.citationstyle.CitationStyleGenerator;
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
import org.jabref.logic.citationstyle.CitationStylePreviewLayout;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.layout.Layout;
import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.layout.LayoutHelper;
import org.jabref.logic.layout.TextBasedPreviewLayout;
import org.jabref.logic.os.OS;
import org.jabref.logic.preview.PreviewLayout;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;

import com.airhacks.afterburner.injection.Injector;
import com.google.common.annotations.VisibleForTesting;

public class ClipboardContentGenerator {

private PreviewPreferences previewPreferences;
private final LayoutFormatterPreferences layoutFormatterPreferences;
private final JournalAbbreviationRepository abbreviationRepository;

public ClipboardContentGenerator(PreviewPreferences previewPreferences,
LayoutFormatterPreferences layoutFormatterPreferences,
JournalAbbreviationRepository abbreviationRepository) {
this.previewPreferences = previewPreferences;
this.layoutFormatterPreferences = layoutFormatterPreferences;
this.abbreviationRepository = abbreviationRepository;
}

public ClipboardContent generate(List<BibEntry> selectedEntries, CitationStyleOutputFormat outputFormat, BibDatabaseContext bibDatabaseContext) throws IOException {
List<String> citations = generateCitations(selectedEntries, outputFormat, bibDatabaseContext);
PreviewLayout previewLayout = previewPreferences.getSelectedPreviewLayout();

// if it is not a citation style take care of the preview
if (!(previewLayout instanceof CitationStylePreviewLayout)) {
return processPreview(citations);
} else {
// if it's generated by a citation style take care of each output format
ClipboardContent content;
return switch (outputFormat) {
case HTML -> processHtml(citations);
case TEXT -> processText(citations);
};
}
}

private List<String> generateCitations(List<BibEntry> selectedEntries, CitationStyleOutputFormat outputFormat, BibDatabaseContext bibDatabaseContext) 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;
PreviewLayout previewLayout = previewPreferences.getSelectedPreviewLayout();

if (previewLayout instanceof CitationStylePreviewLayout citationStyleLayout) {
styleSource = citationStyleLayout.getText();
}

if (styleSource != null) {
return CitationStyleGenerator.generateBibliographies(
selectedEntries,
styleSource,
outputFormat,
bibDatabaseContext,
Injector.instantiateModelOrService(BibEntryTypesManager.class));
} else {
return generateTextBasedPreviewLayoutCitations(selectedEntries, bibDatabaseContext);
}
}

/**
* Generates a plain text string out of the preview (based on {@link org.jabref.logic.layout.TextBasedPreviewLayout} or {@link org.jabref.logic.bst.BstPreviewLayout})
* and copies it additionally to the html to the clipboard (WYSIWYG Editors use the HTML, plain text editors the text)
*/
@VisibleForTesting
static ClipboardContent processPreview(List<String> citations) {
ClipboardContent content = new ClipboardContent();
content.putHtml(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations));
content.putString(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations));
return content;
}

/**
* Joins every citation with a newline and returns it.
*/
@VisibleForTesting
static ClipboardContent processText(List<String> citations) {
ClipboardContent content = new ClipboardContent();
content.putString(String.join(CitationStyleOutputFormat.TEXT.getLineSeparator(), citations));
return content;
}

/**
* Inserts each citation into a HTML body and copies it to the clipboard.
* The given preview is based on {@link org.jabref.logic.citationstyle.CitationStylePreviewLayout}.
*/
@VisibleForTesting
static ClipboardContent processHtml(List<String> citations) {
String result = "<!DOCTYPE html>" + OS.NEWLINE +
"<html>" + OS.NEWLINE +
" <head>" + OS.NEWLINE +
" <meta charset=\"utf-8\">" + OS.NEWLINE +
" </head>" + OS.NEWLINE +
" <body>" + OS.NEWLINE + OS.NEWLINE;

result += String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations);
result += OS.NEWLINE +
" </body>" + OS.NEWLINE +
"</html>" + OS.NEWLINE;

ClipboardContent content = new ClipboardContent();
content.putString(result);
content.putHtml(result);
return content;
}

private List<String> generateTextBasedPreviewLayoutCitations(List<BibEntry> selectedEntries, BibDatabaseContext bibDatabaseContext) throws IOException {
TextBasedPreviewLayout customPreviewLayout = previewPreferences.getCustomPreviewLayout();
StringReader customLayoutReader = new StringReader(customPreviewLayout.getText().replace("__NEWLINE__", "\n"));
Layout layout = new LayoutHelper(customLayoutReader, layoutFormatterPreferences, abbreviationRepository).getLayoutFromText();
List<String> citations = new ArrayList<>(selectedEntries.size());
for (BibEntry entry : selectedEntries) {
citations.add(layout.doLayout(entry, bibDatabaseContext.getDatabase()));
}
return citations;
}
}
120 changes: 6 additions & 114 deletions src/main/java/org/jabref/gui/preview/CopyCitationAction.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package org.jabref.gui.preview;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javafx.scene.input.ClipboardContent;
Expand All @@ -14,22 +11,13 @@
import org.jabref.gui.actions.ActionHelper;
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.citationstyle.CitationStyleGenerator;
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
import org.jabref.logic.citationstyle.CitationStylePreviewLayout;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.layout.Layout;
import org.jabref.logic.layout.LayoutHelper;
import org.jabref.logic.layout.TextBasedPreviewLayout;
import org.jabref.logic.os.OS;
import org.jabref.logic.preview.PreviewLayout;
import org.jabref.logic.util.BackgroundTask;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;

import com.airhacks.afterburner.injection.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -47,8 +35,7 @@ public class CopyCitationAction extends SimpleCommand {
private final DialogService dialogService;
private final ClipBoardManager clipBoardManager;
private final TaskExecutor taskExecutor;
private final GuiPreferences preferences;
private final JournalAbbreviationRepository abbreviationRepository;
private final ClipboardContentGenerator clipboardContentGenerator;

public CopyCitationAction(CitationStyleOutputFormat outputFormat,
DialogService dialogService,
Expand All @@ -63,8 +50,7 @@ public CopyCitationAction(CitationStyleOutputFormat outputFormat,
this.selectedEntries = stateManager.getSelectedEntries();
this.clipBoardManager = clipBoardManager;
this.taskExecutor = taskExecutor;
this.preferences = preferences;
this.abbreviationRepository = abbreviationRepository;
this.clipboardContentGenerator = new ClipboardContentGenerator(preferences.getPreviewPreferences(), preferences.getLayoutFormatterPreferences(), abbreviationRepository);

this.executable.bind(ActionHelper.needsEntriesSelected(stateManager));
}
Expand All @@ -77,106 +63,12 @@ public void execute() {
.executeWith(taskExecutor);
}

private List<String> 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;
PreviewLayout previewLayout = preferences.getPreviewPreferences().getSelectedPreviewLayout();

if (previewLayout instanceof CitationStylePreviewLayout citationStyleLayout) {
styleSource = citationStyleLayout.getText();
}

if (styleSource != null) {
return CitationStyleGenerator.generateBibliographies(
selectedEntries,
styleSource,
outputFormat,
stateManager.getActiveDatabase().get(),
Injector.instantiateModelOrService(BibEntryTypesManager.class));
} else {
return generateTextBasedPreviewLayoutCitations();
}
}

private List<String> generateTextBasedPreviewLayoutCitations() throws IOException {
if (stateManager.getActiveDatabase().isEmpty()) {
return Collections.emptyList();
}

TextBasedPreviewLayout customPreviewLayout = preferences.getPreviewPreferences().getCustomPreviewLayout();
StringReader customLayoutReader = new StringReader(customPreviewLayout.getText().replace("__NEWLINE__", "\n"));
Layout layout = new LayoutHelper(customLayoutReader, preferences.getLayoutFormatterPreferences(), abbreviationRepository)
.getLayoutFromText();

List<String> citations = new ArrayList<>(selectedEntries.size());
for (BibEntry entry : selectedEntries) {
citations.add(layout.doLayout(entry, stateManager.getActiveDatabase().get().getDatabase()));
}
return citations;
}

/**
* Generates a plain text string out of the preview and copies it additionally to the html to the clipboard (WYSIWYG Editors use the HTML, plain text editors the text)
*/
protected static ClipboardContent processPreview(List<String> citations) {
ClipboardContent content = new ClipboardContent();
content.putHtml(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations));
content.putString(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations));
return content;
private ClipboardContent generateCitations() throws IOException {
return clipboardContentGenerator.generate(selectedEntries, outputFormat, stateManager.getActiveDatabase().get());
}

/**
* Joins every citation with a newline and returns it.
*/
protected static ClipboardContent processText(List<String> citations) {
ClipboardContent content = new ClipboardContent();
content.putString(String.join(CitationStyleOutputFormat.TEXT.getLineSeparator(), citations));
return content;
}

/**
* Inserts each citation into a HTML body and copies it to the clipboard
*/
protected static ClipboardContent processHtml(List<String> citations) {
String result = "<!DOCTYPE html>" + OS.NEWLINE +
"<html>" + OS.NEWLINE +
" <head>" + OS.NEWLINE +
" <meta charset=\"utf-8\">" + OS.NEWLINE +
" </head>" + OS.NEWLINE +
" <body>" + OS.NEWLINE + OS.NEWLINE;

result += String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations);
result += OS.NEWLINE +
" </body>" + OS.NEWLINE +
"</html>" + OS.NEWLINE;

ClipboardContent content = new ClipboardContent();
content.putString(result);
content.putHtml(result);
return content;
}

private void setClipBoardContent(List<String> citations) {
PreviewLayout previewLayout = preferences.getPreviewPreferences().getSelectedPreviewLayout();

// if it's not a citation style take care of the preview
if (!(previewLayout instanceof CitationStylePreviewLayout)) {
clipBoardManager.setContent(processPreview(citations));
} else {
// if it's generated by a citation style take care of each output format
ClipboardContent content;
switch (outputFormat) {
case HTML -> content = processHtml(citations);
case TEXT -> content = processText(citations);
default -> {
LOGGER.warn("unknown output format: '{}', processing it via the default.", outputFormat);
content = processText(citations);
}
}
clipBoardManager.setContent(content);
}

private void setClipBoardContent(ClipboardContent clipBoardContent) {
clipBoardManager.setContent(clipBoardContent);
dialogService.notify(Localization.lang("Copied %0 citations.", String.valueOf(selectedEntries.size())));
}
}
Loading
Loading