diff --git a/CHANGELOG.md b/CHANGELOG.md index 369503e2f2e..1afa7a60d65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Added 'Filter All' and 'Filter None' buttons with corresponding functionality to Quality Check tool. - We increased the size of the keywords and file text areas in the entry editor - When the entry that is currently shown in the entry editor is deleted externally, the editor is now closed automatically [#2946](https://github.com/JabRef/jabref/issues/2946) +- We added reordering of file and link entries in the `General`-Tab [3165, comment](https://github.com/JabRef/jabref/issues/3165#issuecomment-326269715) ### Fixed diff --git a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java index 7a4328de855..9ffe3125c10 100644 --- a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java +++ b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -8,5 +8,6 @@ public class DragAndDropDataFormats { public static final DataFormat GROUP = new DataFormat("dnd/org.jabref.model.groups.GroupTreeNode"); + public static final DataFormat LINKED_FILE = new DataFormat("dnd/org.jabref.model.entry.LinkedFile"); public static final DataFormat ENTRIES = new DataFormat("application/x-java-jvm-local-objectref"); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index d76ca36f46c..2ae65c0709e 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -3,6 +3,7 @@ import java.util.Optional; import javafx.beans.binding.Bindings; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.Node; @@ -14,14 +15,19 @@ import javafx.scene.control.ProgressBar; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.Tooltip; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DragEvent; +import javafx.scene.input.Dragboard; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; +import javafx.scene.input.TransferMode; import javafx.scene.layout.HBox; import javafx.scene.text.Text; import org.jabref.Globals; import org.jabref.gui.DialogService; +import org.jabref.gui.DragAndDropDataFormats; import org.jabref.gui.autocompleter.AutoCompleteSuggestionProvider; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.util.ControlHelper; @@ -31,6 +37,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; import de.jensd.fx.glyphs.materialdesignicons.utils.MaterialDesignIconFactory; @@ -49,12 +56,66 @@ public LinkedFilesEditor(String fieldName, DialogService dialogService, BibDatab .withTooltip(LinkedFileViewModel::getDescription) .withGraphic(LinkedFilesEditor::createFileDisplay) .withContextMenu(this::createContextMenuForFile) - .withOnMouseClickedEvent(this::handleItemMouseClick); + .withOnMouseClickedEvent(this::handleItemMouseClick) + .setOnDragDetected(this::handleOnDragDetected) + .setOnDragDropped(this::handleOnDragDropped) + .setOnDragOver(this::handleOnDragOver); + listView.setCellFactory(cellFactory); + Bindings.bindContent(listView.itemsProperty().get(), viewModel.filesProperty()); setUpKeyBindings(); } + private void handleOnDragOver(LinkedFileViewModel originalItem, DragEvent event) { + if ((event.getGestureSource() != originalItem) && event.getDragboard().hasContent(DragAndDropDataFormats.LINKED_FILE)) { + event.acceptTransferModes(TransferMode.MOVE); + } + } + + private void handleOnDragDetected(LinkedFileViewModel linkedFile, MouseEvent event) { + LinkedFile selectedItem = listView.getSelectionModel().getSelectedItem().getFile(); + if (selectedItem != null) { + ClipboardContent content = new ClipboardContent(); + Dragboard dragboard = listView.startDragAndDrop(TransferMode.MOVE); + //We have to use the model class here, as the content of the dragboard must be serializable + content.put(DragAndDropDataFormats.LINKED_FILE, selectedItem); + dragboard.setContent(content); + } + event.consume(); + } + + private void handleOnDragDropped(LinkedFileViewModel originalItem, DragEvent event) { + Dragboard dragboard = event.getDragboard(); + boolean success = false; + + ObservableList items = listView.getItems(); + + if (dragboard.hasContent(DragAndDropDataFormats.LINKED_FILE)) { + + LinkedFile linkedFile = (LinkedFile) dragboard.getContent(DragAndDropDataFormats.LINKED_FILE); + LinkedFileViewModel transferedItem = null; + int draggedIdx = 0; + for (int i = 0; i < items.size(); i++) { + if (items.get(i).getFile().equals(linkedFile)) { + draggedIdx = i; + transferedItem = items.get(i); + break; + } + } + + int thisIdx = items.indexOf(originalItem); + items.set(draggedIdx, originalItem); + items.set(thisIdx, transferedItem); + + event.setDropCompleted(success); + event.consume(); + success = true; + } + event.setDropCompleted(success); + event.consume(); + } + private static Node createFileDisplay(LinkedFileViewModel linkedFile) { Text icon = MaterialDesignIconFactory.get().createIcon(linkedFile.getTypeIcon()); Text text = new Text(linkedFile.getLink()); @@ -79,15 +140,15 @@ private void setUpKeyBindings() { Optional keyBinding = Globals.getKeyPrefs().mapToKeyBinding(event); if (keyBinding.isPresent()) { switch (keyBinding.get()) { - case DELETE_ENTRY: - LinkedFileViewModel selectedItem = listView.getSelectionModel().getSelectedItem(); - if (selectedItem != null) { - viewModel.deleteFile(selectedItem); - } - event.consume(); - break; - default: - // Pass other keys to children + case DELETE_ENTRY: + LinkedFileViewModel selectedItem = listView.getSelectionModel().getSelectedItem(); + if (selectedItem != null) { + viewModel.deleteFile(selectedItem); + } + event.consume(); + break; + default: + // Pass other keys to children } } }); @@ -160,6 +221,7 @@ private ContextMenu createContextMenuForFile(LinkedFileViewModel linkedFile) { } private void handleItemMouseClick(LinkedFileViewModel linkedFile, MouseEvent event) { + if (event.getButton().equals(MouseButton.PRIMARY) && (event.getClickCount() == 2)) { // Double click -> edit linkedFile.edit(); diff --git a/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java b/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java index dca5c06c8c3..e33bd4cbccc 100644 --- a/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java +++ b/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java @@ -7,6 +7,7 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.Tooltip; +import javafx.scene.input.DragEvent; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Paint; import javafx.scene.text.Text; @@ -30,6 +31,11 @@ public class ViewModelListCellFactory implements Callback, ListCe private BiConsumer toOnMouseClickedEvent; private Callback toStyleClass; private Callback toContextMenu; + private BiConsumer toOnDragDetected; + private BiConsumer toOnDragDropped; + private BiConsumer toOnDragEntered; + private BiConsumer toOnDragExited; + private BiConsumer toOnDragOver; public ViewModelListCellFactory withText(Callback toText) { this.toText = toText; @@ -76,6 +82,31 @@ public ViewModelListCellFactory withOnMouseClickedEvent( return this; } + public ViewModelListCellFactory setOnDragDetected(BiConsumer toOnDragDetected) { + this.toOnDragDetected = toOnDragDetected; + return this; + } + + public ViewModelListCellFactory setOnDragDropped(BiConsumer toOnDragDropped) { + this.toOnDragDropped = toOnDragDropped; + return this; + } + + public ViewModelListCellFactory setOnDragEntered(BiConsumer toOnDragEntered) { + this.toOnDragEntered = toOnDragEntered; + return this; + } + + public ViewModelListCellFactory setOnDragExited(BiConsumer toOnDragExited) { + this.toOnDragExited = toOnDragExited; + return this; + } + + public ViewModelListCellFactory setOnDragOver(BiConsumer toOnDragOver) { + this.toOnDragOver = toOnDragOver; + return this; + } + @Override public ListCell call(ListView param) { @@ -86,7 +117,7 @@ protected void updateItem(T item, boolean empty) { super.updateItem(item, empty); T viewModel = getItem(); - if (empty || viewModel == null) { + if (empty || (viewModel == null)) { setText(null); setGraphic(null); setOnMouseClicked(null); @@ -113,6 +144,21 @@ protected void updateItem(T item, boolean empty) { if (toContextMenu != null) { setContextMenu(toContextMenu.call(viewModel)); } + if (toOnDragDetected != null) { + setOnDragDetected(event -> toOnDragDetected.accept(viewModel, event)); + } + if (toOnDragDropped != null) { + setOnDragDropped(event -> toOnDragDropped.accept(viewModel, event)); + } + if (toOnDragEntered != null) { + setOnDragEntered(event -> toOnDragEntered.accept(viewModel, event)); + } + if (toOnDragExited != null) { + setOnDragExited(event -> toOnDragExited.accept(viewModel, event)); + } + if (toOnDragOver != null) { + setOnDragOver(event -> toOnDragOver.accept(viewModel, event)); + } } getListView().refresh(); } diff --git a/src/main/java/org/jabref/model/entry/LinkedFile.java b/src/main/java/org/jabref/model/entry/LinkedFile.java index 22094eecd05..ecd7bb78b56 100644 --- a/src/main/java/org/jabref/model/entry/LinkedFile.java +++ b/src/main/java/org/jabref/model/entry/LinkedFile.java @@ -1,5 +1,6 @@ package org.jabref.model.entry; +import java.io.Serializable; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; @@ -14,7 +15,8 @@ /** * Represents the link to an external file (e.g. associated PDF file). */ -public class LinkedFile { +//Serialiable is required for drag and drop +public class LinkedFile implements Serializable { private static final LinkedFile NULL_OBJECT = new LinkedFile("", "", ""); private String description;