Skip to content

Commit

Permalink
Enable drag'n'drop from maintable to external application (#11846)
Browse files Browse the repository at this point in the history
* Create ClipboardContentGenerator out of CopyCitationAction

* Fix method name

* Add external drag'n'drop feature

* Add link

* Update CHANGELOG.md

Co-authored-by: Christoph <siedlerkiller@gmail.com>

---------

Co-authored-by: Christoph <siedlerkiller@gmail.com>
  • Loading branch information
koppor and Siedlerchr authored Sep 29, 2024
1 parent 1df99e2 commit 6fe8f2d
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 123 deletions.
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

0 comments on commit 6fe8f2d

Please sign in to comment.