-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
6
-
x86
-
linux
FULL PRODUCT VERSION :
> java -version
java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot(TM) 64-Bit Server VM (build 10.0-b22, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
> uname -a
Linux lichtenstein 2.6.25.11-0.1-default #1 SMP 2008-07-13 20:48:28 +0200 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
I am trying to implement drag and drop for a JList. Only internal D&D is supported, the stuff dragged are local objects.
At createTransferable, I extract all selected values with the JList’s getSelectedValues() into an Object[] and wrap these in a DataHandler with an appropriate MIME type. importData() then extracts this array and inserts them in the same order at the index given by JList.DropLocation’s getIndex(). (I have the JList’s drop mode set to DropMode.INSERT.)
Finally, exportDone() simply removes all selected elements by cycling through getSelectedIndices().
Most of the time, this works fine, but sometimes it doesn’t. Particularly, if I drop an element just before itself, it gets deleted! Except for the last object in the list, for which an ArrayIndexOutOfBoundsException is thrown.
Through debugging, I see that after importData and at the start of exportDone, the data is properly imported, however: in the general case, the inserted rows are not selected and everything works fine. If however the element is inserted directly before a selected element, it gets selected as well and thus removed in the ensuing exportDone.
The same behavior can be observed when the first element of the list is selected and one invokes an add() on the DefaultListModel: then both the first (new) element and the second are selected. Otherwise, only the already selected element remains selected.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
An SSCCE below. Both effects can be observed here: add some formulas through the menu, then select the first one, and add one more: both are selected.
Also, try to drag a formula just before itself. Debug with breakpoint at exportDone and you will see the cause.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The items that were selected should remain selected, the inserted items should not be.
ACTUAL -
Sometimes, the dragged item disappears completely.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
From time to time:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 6 >= 6
at java.util.Vector.elementAt(Vector.java:427)
at javax.swing.DefaultListModel.remove(DefaultListModel.java:476)
at FormulaTransferHandler.exportDone(FormulaTransferHandler.java:84)
at javax.swing.TransferHandler$DragHandler.dragDropEnd(TransferHandler.java:1567)
at java.awt.dnd.DragSourceContext.dragDropEnd(DragSourceContext.java:399)
at sun.awt.dnd.SunDragSourceContextPeer$EventDispatcher.run(SunDragSourceContextPeer.java:432)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class UIBuilder {
public UIBuilder() {
super();
this.frame = new JFrame();
this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.queryField = this.createQueryField();
this.initMenu();
}
private final JFrame frame;
final private JList queryField;
/**
* Initialize the edit area. Make a JList, enable dragging with a custom
* TransferHandler class
*
* @return The list which contains the formulas being edited.
*/
private JList createQueryField() {
final JList result = new JList(new DefaultListModel());
result.setDragEnabled(true);
result.setTransferHandler(new FormulaTransferHandler());
result.setDropMode(DropMode.INSERT);
this.frame.getContentPane().add(result, BorderLayout.CENTER);
return result;
}
private void initMenu() {
final JMenuBar mb = new JMenuBar();
this.frame.setJMenuBar(mb);
final JMenu atomicMenu = new JMenu("Formula");
mb.add(atomicMenu);
final JMenuItem addItem = new JMenuItem("Add");
atomicMenu.add(addItem);
addItem.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent evt) {
((DefaultListModel) UIBuilder.this.queryField.getModel()).add(0,
new Formula());
}
});
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final UIBuilder MonaSearch = new UIBuilder();
MonaSearch.frame.setSize(400, 400);
MonaSearch.frame.setVisible(true);
}
});
}
}
import java.awt.datatransfer.*;
import java.io.IOException;
import javax.activation.ActivationDataFlavor;
import javax.activation.DataHandler;
import javax.swing.*;
/**
* A transfer handler for formulas. Based on Sun’s Swing DnD tutorial.
*/
public class FormulaTransferHandler extends TransferHandler {
/**
* Initialize the formula transfer handler. Sets the supported flavors. For
* now these are:
* <ul>
* <li>new ActivationDataFlavor(Formula.class,
* DataFlavor.javaJVMLocalObjectMimeType, "Arrays of formulas");</li>
* </ul>
*/
public FormulaTransferHandler() {
this.localObjectFlavor = new ActivationDataFlavor(Formula[].class,
DataFlavor.javaJVMLocalObjectMimeType, "Arrays of formulas");
}
/**
* The data flavor for formulas local to the JVM.
*/
private final DataFlavor localObjectFlavor;
@Override
protected Transferable createTransferable(final JComponent c) {
System.out.println("createTransferable");
final JList formulaList = (JList) c;
final Object[] selectedValues = formulaList.getSelectedValues();
final Formula[] transferedFormulas = new Formula[selectedValues.length];
for (int i = 0; i < selectedValues.length; i++) {
transferedFormulas[i] = (Formula) selectedValues[i];
}
return new DataHandler(transferedFormulas, this.localObjectFlavor
.getMimeType());
// TODO: add support via serialization?
}
@Override
public boolean canImport(final TransferHandler.TransferSupport info) {
// only support drop, I don’t see a use for paste here
if (!info.isDrop()) {
return false;
}
if (!info.isDataFlavorSupported(this.localObjectFlavor)) {
// TODO: allow serialization?
return false;
}
return true;
}
@Override
public int getSourceActions(final JComponent c) {
return TransferHandler.COPY_OR_MOVE;
}
@Override
public boolean importData(final TransferHandler.TransferSupport info) {
if (this.canImport(info)) {
try {
final Formula[] formulas = (Formula[]) info.getTransferable()
.getTransferData(this.localObjectFlavor);
int index = ((JList.DropLocation) info.getDropLocation()).getIndex();
final DefaultListModel listModel = (DefaultListModel) ((JList) info
.getComponent()).getModel();
for (final Formula element : formulas) {
listModel.add(index++, element);
}
return true;
} catch (final UnsupportedFlavorException ufe) {
ufe.printStackTrace();
} catch (final IOException ioe) {
ioe.printStackTrace();
}
}
return false;
}
@Override
protected void exportDone(final JComponent c, final Transferable data,
final int action) {
if (action == TransferHandler.MOVE) {
final JList list = (JList) c;
final int[] indices = list.getSelectedIndices();
final DefaultListModel model = (DefaultListModel) list.getModel();
for (int i = 0; i < indices.length; i++) {
model.remove(indices[i]);
}
}
}
}
public class Formula {
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
There is a rather complex workaround, which involves storing the selectedIndices from the JList in the TransferHandler, computing how the indices change when inserting and removing from those indices at exportDone. This is, however, very cumbersome.
> java -version
java version "1.6.0_06"
Java(TM) SE Runtime Environment (build 1.6.0_06-b02)
Java HotSpot(TM) 64-Bit Server VM (build 10.0-b22, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
> uname -a
Linux lichtenstein 2.6.25.11-0.1-default #1 SMP 2008-07-13 20:48:28 +0200 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
I am trying to implement drag and drop for a JList. Only internal D&D is supported, the stuff dragged are local objects.
At createTransferable, I extract all selected values with the JList’s getSelectedValues() into an Object[] and wrap these in a DataHandler with an appropriate MIME type. importData() then extracts this array and inserts them in the same order at the index given by JList.DropLocation’s getIndex(). (I have the JList’s drop mode set to DropMode.INSERT.)
Finally, exportDone() simply removes all selected elements by cycling through getSelectedIndices().
Most of the time, this works fine, but sometimes it doesn’t. Particularly, if I drop an element just before itself, it gets deleted! Except for the last object in the list, for which an ArrayIndexOutOfBoundsException is thrown.
Through debugging, I see that after importData and at the start of exportDone, the data is properly imported, however: in the general case, the inserted rows are not selected and everything works fine. If however the element is inserted directly before a selected element, it gets selected as well and thus removed in the ensuing exportDone.
The same behavior can be observed when the first element of the list is selected and one invokes an add() on the DefaultListModel: then both the first (new) element and the second are selected. Otherwise, only the already selected element remains selected.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
An SSCCE below. Both effects can be observed here: add some formulas through the menu, then select the first one, and add one more: both are selected.
Also, try to drag a formula just before itself. Debug with breakpoint at exportDone and you will see the cause.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The items that were selected should remain selected, the inserted items should not be.
ACTUAL -
Sometimes, the dragged item disappears completely.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
From time to time:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 6 >= 6
at java.util.Vector.elementAt(Vector.java:427)
at javax.swing.DefaultListModel.remove(DefaultListModel.java:476)
at FormulaTransferHandler.exportDone(FormulaTransferHandler.java:84)
at javax.swing.TransferHandler$DragHandler.dragDropEnd(TransferHandler.java:1567)
at java.awt.dnd.DragSourceContext.dragDropEnd(DragSourceContext.java:399)
at sun.awt.dnd.SunDragSourceContextPeer$EventDispatcher.run(SunDragSourceContextPeer.java:432)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class UIBuilder {
public UIBuilder() {
super();
this.frame = new JFrame();
this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.queryField = this.createQueryField();
this.initMenu();
}
private final JFrame frame;
final private JList queryField;
/**
* Initialize the edit area. Make a JList, enable dragging with a custom
* TransferHandler class
*
* @return The list which contains the formulas being edited.
*/
private JList createQueryField() {
final JList result = new JList(new DefaultListModel());
result.setDragEnabled(true);
result.setTransferHandler(new FormulaTransferHandler());
result.setDropMode(DropMode.INSERT);
this.frame.getContentPane().add(result, BorderLayout.CENTER);
return result;
}
private void initMenu() {
final JMenuBar mb = new JMenuBar();
this.frame.setJMenuBar(mb);
final JMenu atomicMenu = new JMenu("Formula");
mb.add(atomicMenu);
final JMenuItem addItem = new JMenuItem("Add");
atomicMenu.add(addItem);
addItem.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent evt) {
((DefaultListModel) UIBuilder.this.queryField.getModel()).add(0,
new Formula());
}
});
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final UIBuilder MonaSearch = new UIBuilder();
MonaSearch.frame.setSize(400, 400);
MonaSearch.frame.setVisible(true);
}
});
}
}
import java.awt.datatransfer.*;
import java.io.IOException;
import javax.activation.ActivationDataFlavor;
import javax.activation.DataHandler;
import javax.swing.*;
/**
* A transfer handler for formulas. Based on Sun’s Swing DnD tutorial.
*/
public class FormulaTransferHandler extends TransferHandler {
/**
* Initialize the formula transfer handler. Sets the supported flavors. For
* now these are:
* <ul>
* <li>new ActivationDataFlavor(Formula.class,
* DataFlavor.javaJVMLocalObjectMimeType, "Arrays of formulas");</li>
* </ul>
*/
public FormulaTransferHandler() {
this.localObjectFlavor = new ActivationDataFlavor(Formula[].class,
DataFlavor.javaJVMLocalObjectMimeType, "Arrays of formulas");
}
/**
* The data flavor for formulas local to the JVM.
*/
private final DataFlavor localObjectFlavor;
@Override
protected Transferable createTransferable(final JComponent c) {
System.out.println("createTransferable");
final JList formulaList = (JList) c;
final Object[] selectedValues = formulaList.getSelectedValues();
final Formula[] transferedFormulas = new Formula[selectedValues.length];
for (int i = 0; i < selectedValues.length; i++) {
transferedFormulas[i] = (Formula) selectedValues[i];
}
return new DataHandler(transferedFormulas, this.localObjectFlavor
.getMimeType());
// TODO: add support via serialization?
}
@Override
public boolean canImport(final TransferHandler.TransferSupport info) {
// only support drop, I don’t see a use for paste here
if (!info.isDrop()) {
return false;
}
if (!info.isDataFlavorSupported(this.localObjectFlavor)) {
// TODO: allow serialization?
return false;
}
return true;
}
@Override
public int getSourceActions(final JComponent c) {
return TransferHandler.COPY_OR_MOVE;
}
@Override
public boolean importData(final TransferHandler.TransferSupport info) {
if (this.canImport(info)) {
try {
final Formula[] formulas = (Formula[]) info.getTransferable()
.getTransferData(this.localObjectFlavor);
int index = ((JList.DropLocation) info.getDropLocation()).getIndex();
final DefaultListModel listModel = (DefaultListModel) ((JList) info
.getComponent()).getModel();
for (final Formula element : formulas) {
listModel.add(index++, element);
}
return true;
} catch (final UnsupportedFlavorException ufe) {
ufe.printStackTrace();
} catch (final IOException ioe) {
ioe.printStackTrace();
}
}
return false;
}
@Override
protected void exportDone(final JComponent c, final Transferable data,
final int action) {
if (action == TransferHandler.MOVE) {
final JList list = (JList) c;
final int[] indices = list.getSelectedIndices();
final DefaultListModel model = (DefaultListModel) list.getModel();
for (int i = 0; i < indices.length; i++) {
model.remove(indices[i]);
}
}
}
}
public class Formula {
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
There is a rather complex workaround, which involves storing the selectedIndices from the JList in the TransferHandler, computing how the indices change when inserting and removing from those indices at exportDone. This is, however, very cumbersome.