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

Implement drag and drop reordering in General files list #3194

Merged
merged 8 commits into from
Sep 5, 2017
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jabref/gui/DragAndDropDataFormats.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
85 changes: 74 additions & 11 deletions src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -49,12 +56,67 @@ public LinkedFilesEditor(String fieldName, DialogService dialogService, BibDatab
.withTooltip(LinkedFileViewModel::getDescription)
.withGraphic(LinkedFilesEditor::createFileDisplay)
.withContextMenu(this::createContextMenuForFile)
.withOnMouseClickedEvent(this::handleItemMouseClick);
.withOnMouseClickedEvent(this::handleItemMouseClick)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koppor The BiConsumer stuff allows to do these fancy things

.setOnDragDetected(this::handleOnDragDetected)
.setOnDragDropped(this::handleOnDragDropped)
.setOnDragOver(this::handleOnDragOver);

listView.setCellFactory(cellFactory);
Bindings.bindContent(listView.itemsProperty().get(), viewModel.filesProperty());

Bindings.bindContentBidirectional(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(@SuppressWarnings("unused") 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<LinkedFileViewModel> items = listView.itemsProperty().get();

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());
Expand All @@ -79,15 +141,15 @@ private void setUpKeyBindings() {
Optional<KeyBinding> 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
}
}
});
Expand Down Expand Up @@ -160,6 +222,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();
Expand Down
48 changes: 47 additions & 1 deletion src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +31,11 @@ public class ViewModelListCellFactory<T> implements Callback<ListView<T>, ListCe
private BiConsumer<T, ? super MouseEvent> toOnMouseClickedEvent;
private Callback<T, String> toStyleClass;
private Callback<T, ContextMenu> toContextMenu;
private BiConsumer<T, ? super MouseEvent> toOnDragDetected;
private BiConsumer<T, ? super DragEvent> toOnDragDropped;
private BiConsumer<T, ? super DragEvent> toOnDragEntered;
private BiConsumer<T, ? super DragEvent> toOnDragExited;
private BiConsumer<T, ? super DragEvent> toOnDragOver;

public ViewModelListCellFactory<T> withText(Callback<T, String> toText) {
this.toText = toText;
Expand Down Expand Up @@ -76,6 +82,31 @@ public ViewModelListCellFactory<T> withOnMouseClickedEvent(
return this;
}

public ViewModelListCellFactory<T> setOnDragDetected(BiConsumer<T, ? super MouseEvent> toOnDragDetected) {
this.toOnDragDetected = toOnDragDetected;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koppor The null check refers to this factory function where it gets assigned

return this;
}

public ViewModelListCellFactory<T> setOnDragDropped(BiConsumer<T, ? super DragEvent> toOnDragDropped) {
this.toOnDragDropped = toOnDragDropped;
return this;
}

public ViewModelListCellFactory<T> setOnDragEntered(BiConsumer<T, ? super DragEvent> toOnDragEntered) {
this.toOnDragEntered = toOnDragEntered;
return this;
}

public ViewModelListCellFactory<T> setOnDragExited(BiConsumer<T, ? super DragEvent> toOnDragExited) {
this.toOnDragExited = toOnDragExited;
return this;
}

public ViewModelListCellFactory<T> setOnDragOver(BiConsumer<T, ? super DragEvent> toOnDragOver) {
this.toOnDragOver = toOnDragOver;
return this;
}

@Override
public ListCell<T> call(ListView<T> param) {

Expand All @@ -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);
Expand All @@ -113,6 +144,21 @@ protected void updateItem(T item, boolean empty) {
if (toContextMenu != null) {
setContextMenu(toContextMenu.call(viewModel));
}
if (toOnDragDetected != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wow, this code looks odd. Is JavaFX really like that?

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();
}
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/org/jabref/model/entry/LinkedFile.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,8 +14,9 @@

/**
* Represents the link to an external file (e.g. associated PDF file).
* This class is {@link Serializable} which is needed for drag and drop in gui
*/
public class LinkedFile {
public class LinkedFile implements Serializable {

private static final LinkedFile NULL_OBJECT = new LinkedFile("", "", "");
private String description;
Expand All @@ -26,6 +28,7 @@ public LinkedFile(String description, String link, String fileType) {
this.link = Objects.requireNonNull(link);
this.fileType = Objects.requireNonNull(fileType);
}

public LinkedFile(String description, URL link, String fileType) {
this(description, Objects.requireNonNull(link).toString(), fileType);
}
Expand Down