I've got a couple of JList both being able to be sources and destinations of DnD COPY actions. They work fine, but for one thing - elements are added at the bottom of the lists, instead at the drop destination row, as I'd like.
Since Oracle BasicDnD example includes this feature (and, in fact, it's the only application I've been able to find through Google with it), I've been taking a look at its source to see if I was able to adapt it. I tried to set the TransferHandler object, which is what I guess I'm missing, but no new behavior appeared. So, what (else) may I be missing/doing wrong?
EDIT1: Below is shown the class I use for these lists.
private class InteractiveJList extends JList implements DragGestureListener,
DragSourceListener, DropTargetListener {
private final DropTarget dropTarget;
private final DragSource dragSource;
private final boolean removeElementsOnFail;
private int[] selectedOnes;
@SuppressWarnings("unchecked")
private InteractiveJList(final ListModel model,
final boolean _removElementsOnFail) {
super(model);
this.dragSource = new DragSource();
this.dragSource
.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY,
this);
this.dropTarget = new DropTarget(this, this);
this.removeElementsOnFail = _removElementsOnFail;
}
@Override
public void dragEnter(final DropTargetDragEvent arg0) {
}
@Override
public void dragExit(final DropTargetEvent arg0) {
}
@Override
public void dragOver(final DropTargetDragEvent arg0) {
}
@Override
public void drop(final DropTargetDropEvent event) {
if (!this.removeElementsOnFail) {
event.rejectDrop();
return;
}
final Transferable transferable = event.getTransferable();
if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
String all;
try {
all = (String) transferable.getTransferData(DataFlavor.stringFlavor);
} catch (final UnsupportedFlavorException | IOException ex) {
event.rejectDrop();
return;
}
event.acceptDrop(DnDConstants.ACTION_COPY);
final StringTokenizer tokenizer = new StringTokenizer(all, "|");
while (tokenizer.hasMoreTokens()) {
((StringListModel) this.getModel()).addElement(tokenizer.nextToken());
}
event.getDropTargetContext().dropComplete(Boolean.TRUE);
} else {
event.rejectDrop();
}
}
@Override
public void dropActionChanged(final DropTargetDragEvent event) {
}
@Override
public void dragEnter(final DragSourceDragEvent arg0) {
}
@Override
public void dragExit(final DragSourceEvent arg0) {
}
@Override
public void dragOver(final DragSourceDragEvent arg0) {
}
@Override
public void dropActionChanged(final DragSourceDragEvent dragSourceEvent) {
}
@Override
public void dragGestureRecognized(final DragGestureEvent dragGestureEvent) {
final Object listSelectedValue = this.getSelectedValue();
this.selectedOnes = this.getSelectedIndices();
final StringBuilder bridge = new StringBuilder();
for (final int x : this.selectedOnes) {
bridge.append(this.getModel().getElementAt(x)).append("|");
}
if (listSelectedValue != null) {
final StringSelection stringTransferable =
new StringSelection(bridge.toString()
.substring(0, bridge.length() - 1));
this.dragSource.startDrag(dragGestureEvent, DragSource.DefaultCopyDrop,
stringTransferable, this);
}
}
@Override
public void dragDropEnd(final DragSourceDropEvent dragSourceDropEvent) {
if (!dragSourceDropEvent.getDropSuccess()) {
if (this.removeElementsOnFail) {
for (final int x : this.selectedOnes) {
((StringListModel) this.getModel()).removeElement(x);
}
}
}
}
}
The boolean flag in the constructor is used because, for one of the lists, if the drop action if rejected, the element must be removed, but not in the another. This way I can handle that. StringListModel
is an extension of AbstractListModel to handle list of Strings. It is able to add elements both at bottom and at a requested position, clears the list, and remove certain elements. It does its stuff, fires the corresponding events and nothing further.
EDIT2: Below is shown my implementation of MadProgrammer's suggestion:
private static class UserTransferHandler extends TransferHandler {
private final JList list;
private UserTransferHandler(final JList list) {
this.list = list;
}
@Override
public boolean canImport(final TransferSupport support) {
System.out.println("canImport");
boolean canImport = false;
if (support.isDataFlavorSupported(UserTransferable.USER_DATA_FLAVOR)) {
final JList.DropLocation dl =
(JList.DropLocation) support.getDropLocation();
if (dl.getIndex() != -1) {
canImport = true;
}
}
return canImport;
}
@Override
protected void exportDone(final JComponent source, final Transferable data,
final int action) {
System.out.println("exportDone");
}
@Override
public boolean importData(final TransferSupport support) {
System.out.println("importData");
boolean accepted = false;
if (support.isDrop()) {
if (support.isDataFlavorSupported(UserTransferable.USER_DATA_FLAVOR)) {
final JList.DropLocation dl =
(JList.DropLocation) support.getDropLocation();
final StringListModel model = (StringListModel) this.list.getModel();
final int index = dl.getIndex();
final boolean insert = dl.isInsert();
final Transferable t = support.getTransferable();
try {
final String dropped = (String) t
.getTransferData(UserTransferable.USER_DATA_FLAVOR);
if (insert) {
if (index >= model.getSize()) {
model.addElement(dropped);
} else {
model.addElementAt(dropped, index);
}
} else {
model.addElement(dropped);
}
accepted = true;
} catch (final Exception e) {
e.printStackTrace();
}
}
}
return accepted;
}
@Override
public int getSourceActions(final JComponent c) {
System.out.println("getSourceActions");
return TransferHandler.COPY_OR_MOVE;
}
@Override
protected Transferable createTransferable(final JComponent c) {
System.out.println("createTransferable");
final String value = this.list.getSelectedValue().toString();
return new UserTransferable(value);
}
}
private static class UserTransferable implements Transferable {
private static final DataFlavor USER_DATA_FLAVOR =
new DataFlavor(String.class, "String");
private final String value;
private UserTransferable(final String value) {
System.out.println("UserTransferable");
this.value = value;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
System.out.println("getTransferDataFlavors");
return new DataFlavor[] { UserTransferable.USER_DATA_FLAVOR };
}
@Override
public boolean isDataFlavorSupported(final DataFlavor flavor) {
System.out.println("isDataFlavorSupported");
return UserTransferable.USER_DATA_FLAVOR.equals(flavor);
}
@Override
public Object getTransferData(final DataFlavor flavor) throws
UnsupportedFlavorException, IOException {
System.out.println("getTransferData");
if (!UserTransferable.USER_DATA_FLAVOR.equals(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
return this.value;
}
}
The print statements are there to get feedback when the methods are called (which is, right now, never).