//(c) Copyright 2011, Scott Vorthmann.
package org.vorthmann.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;
import java.util.logging.Logger;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JList;
public class ReorderableJList<E> extends JList<E> implements DragSourceListener,
DropTargetListener, DragGestureListener
{
// Initializing it this way just ensures that any copied code uses the correct class name for a static Logger in any class.
private static final String loggerClassName = new Throwable().getStackTrace()[0].getClassName();
private static final Logger logger = Logger.getLogger(loggerClassName);
static DataFlavor localObjectFlavor;
static {
try {
localObjectFlavor = new DataFlavor(
DataFlavor.javaJVMLocalObjectMimeType );
} catch ( ClassNotFoundException cnfe ) {
cnfe.printStackTrace();
}
}
static DataFlavor[] supportedFlavors = { localObjectFlavor };
DragSource dragSource;
DropTarget dropTarget;
Object dropTargetCell;
int draggedIndex = -1;
private final ListMoveListener moves;
private final Class<E> elementClass;
public ReorderableJList ( DefaultListModel<E> listModel, ListMoveListener moves, Class<E> listElementClass )
{
super();
this.elementClass = listElementClass;
this .moves = moves;
setCellRenderer( new ReorderableListCellRenderer() );
setModel( listModel );
dragSource = new DragSource();
dragSource .createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_MOVE, this );
dropTarget = new DropTarget( this, this );
}
// DragGestureListener
@Override
public void dragGestureRecognized( DragGestureEvent dge )
{
// System.out.println( "dragGestureRecognized" );
// find object at this x,y
Point clickPoint = dge.getDragOrigin();
int index = locationToIndex( clickPoint );
if ( index == -1 )
return;
Object target = getModel().getElementAt( index );
Transferable trans = new RJLTransferable( target );
draggedIndex = index;
dragSource.startDrag( dge, Cursor.getDefaultCursor(), trans, this );
}
// DragSourceListener events
@Override
public void dragDropEnd( DragSourceDropEvent dsde )
{
// System.out.println( "dragDropEnd()" );
dropTargetCell = null;
draggedIndex = -1;
repaint();
}
@Override
public void dragEnter( DragSourceDragEvent dsde ) {}
@Override
public void dragExit( DragSourceEvent dse ) {}
@Override
public void dragOver( DragSourceDragEvent dsde ) {}
@Override
public void dropActionChanged( DragSourceDragEvent dsde ) {}
// DropTargetListener events
@Override
public void dragEnter( DropTargetDragEvent dtde )
{
// System.out.println( "dragEnter" );
if ( dtde.getSource() != dropTarget )
dtde.rejectDrag();
else {
dtde.acceptDrag( DnDConstants.ACTION_COPY_OR_MOVE );
// System.out.println( "accepted dragEnter" );
}
}
@Override
public void dragExit( DropTargetEvent dte ) {}
@Override
public void dragOver( DropTargetDragEvent dtde )
{
// figure out which cell it's over, no drag to self
if ( dtde.getSource() != dropTarget )
dtde.rejectDrag();
Point dragPoint = dtde.getLocation();
int index = locationToIndex( dragPoint );
if ( index == -1 )
dropTargetCell = null;
else
dropTargetCell = getModel().getElementAt( index );
repaint();
}
@Override
public void drop( DropTargetDropEvent dtde )
{
// System.out.println( "drop()!" );
if ( dtde.getSource() != dropTarget ) {
// System.out.println( "rejecting for bad source ("
// + dtde.getSource().getClass().getName() + ")" );
dtde.rejectDrop();
return;
}
Point dropPoint = dtde.getLocation();
int index = locationToIndex( dropPoint );
// System.out.println( "drop index is " + index );
boolean dropped = false;
try {
if ( (index == -1) || (index == draggedIndex) ) {
// System.out.println( "dropped onto self" );
dtde.rejectDrop();
return;
}
dtde.acceptDrop( DnDConstants.ACTION_MOVE );
// System.out.println( "accepted" );
Object draggedObj = dtde.getTransferable().getTransferData( localObjectFlavor );
Class<?> draggedObjClass = draggedObj.getClass();
if(!elementClass.isAssignableFrom(draggedObjClass)) {
String msg = "Unsupported type " + draggedObjClass.getName() + " was dropped. Expected " + elementClass.getName();
logger.fine(msg);
// System.out.println( msg );
dtde.rejectDrop();
return;
}
E dragged = elementClass.cast(draggedObj);
// move items - note that indicies for insert will
// change if [removed] source was before target
// System.out.println( "drop " + draggedIndex + " to " + index );
boolean sourceBeforeTarget = (draggedIndex < index);
// System.out.println( "source is" + (sourceBeforeTarget ? "" : " not") + " before target" );
// System.out.println( "insert at " + (sourceBeforeTarget ? index - 1 : index) );
DefaultListModel<E> mod = (DefaultListModel<E>) getModel();
// an alternative, from ListDataListener tutorial
// Object aObject = listModel.getElementAt(a);
// Object bObject = listModel.getElementAt(b);
// listModel.set(a, bObject);
// listModel.set(b, aObject);
moves .startMove();
mod .remove( draggedIndex );
mod .add( (sourceBeforeTarget ? index - 1 : index), dragged );
moves .endMove();
dropped = true;
} catch ( Exception e ) {
e.printStackTrace();
}
dtde.dropComplete( dropped );
}
@Override
public void dropActionChanged( DropTargetDragEvent dtde ) {}
public interface ListMoveListener
{
void startMove();
void endMove();
}
class RJLTransferable implements Transferable
{
Object object;
public RJLTransferable( Object o )
{
object = o;
}
@Override
public Object getTransferData( DataFlavor df )
throws UnsupportedFlavorException, IOException
{
if ( isDataFlavorSupported( df ) )
return object;
else
throw new UnsupportedFlavorException( df );
}
@Override
public boolean isDataFlavorSupported( DataFlavor df )
{
return (df.equals( localObjectFlavor ));
}
@Override
public DataFlavor[] getTransferDataFlavors()
{
return supportedFlavors;
}
}
class ReorderableListCellRenderer extends DefaultListCellRenderer
{
boolean isTargetCell;
boolean isLastItem;
public ReorderableListCellRenderer()
{
super();
}
@Override
public Component getListCellRendererComponent( JList<?> list,
Object value, int index, boolean isSelected, boolean hasFocus )
{
isTargetCell = (value == dropTargetCell);
isLastItem = (index == list .getModel() .getSize() - 1);
boolean showSelected = isSelected & (dropTargetCell == null);
return super.getListCellRendererComponent( list, value, index, showSelected, hasFocus );
}
@Override
public void paintComponent( Graphics g )
{
super.paintComponent( g );
if ( isTargetCell ) {
g .setColor( Color.red );
g .drawRect( 0, 0, getSize().width, 1 );
}
}
}
}