001package jmri.util.iharder.dnd;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006/**
007 * An extension of {@link javax.swing.JList} that supports drag and drop to
008 * rearrange its contents and to move objects in and out of the list. The
009 * objects in the list will be passed either as a String by calling the object's
010 * {@code toString()} object, or if your drag and drop target accepts the
011 * {@link TransferableObject#DATA_FLAVOR} data flavor then the actual object
012 * will be passed.
013 * <p>
014 * I'm releasing this code into the Public Domain. Enjoy.
015 *
016 * @author Robert Harder rharder@usa.net
017 * @version 1.1
018 */
019public class DnDList<E>
020        extends javax.swing.JList<E>
021        implements java.awt.dnd.DropTargetListener,
022        java.awt.dnd.DragSourceListener,
023        java.awt.dnd.DragGestureListener {
024
025    private java.awt.dnd.DragSource dragSource = null;
026
027    private int sourceIndex = -1;
028
029    /**
030     * Constructs a default {@link DnDList} using a
031     * {@link javax.swing.DefaultListModel}.
032     *
033     * @since 1.1
034     */
035    public DnDList() {
036        super(new javax.swing.DefaultListModel<E>());
037        initComponents();
038    }   // end constructor
039
040    /**
041     * Constructs a {@link DnDList} using the passed list model that must be
042     * extended from {@link javax.swing.DefaultListModel}.
043     *
044     * @param model The model to use
045     * @since 1.1
046     */
047    public DnDList(javax.swing.DefaultListModel<E> model) {
048        super(model);
049        initComponents();
050    }   // end constructor
051
052    /**
053     * Constructs a {@link DnDList} by filling in a
054     * {@link javax.swing.DefaultListModel} with the passed array of objects.
055     *
056     * @param data The data from which to construct a list
057     * @since 1.1
058     */
059    public DnDList(E[] data) {
060        this();
061        ((javax.swing.DefaultListModel<E>) getModel()).copyInto(data);
062    }   // end constructor
063
064    /**
065     * Constructs a {@link DnDList} by filling in a
066     * {@link javax.swing.DefaultListModel} with the passed
067     * {@link java.util.Vector} of objects.
068     *
069     * @param data The data from which to construct a list
070     * @since 1.1
071     */
072    public DnDList(java.util.Vector<E> data) {
073        this();
074        ((javax.swing.DefaultListModel<E>) getModel()).copyInto(data.toArray());
075    }   // end constructor
076
077    private void initComponents() {
078        dragSource = new java.awt.dnd.DragSource();
079        dragSource.createDefaultDragGestureRecognizer(this, java.awt.dnd.DnDConstants.ACTION_MOVE, this);
080    }   // end initComponents
081
082    /* ********  D R A G   G E S T U R E   L I S T E N E R   M E T H O D S  ******** */
083    @Override
084    public void dragGestureRecognized(java.awt.dnd.DragGestureEvent event) {   //System.out.println( "DragGestureListener.dragGestureRecognized" );
085        final E selected = getSelectedValue();
086        if (selected != null) {
087            sourceIndex = getSelectedIndex();
088            java.awt.datatransfer.Transferable transfer = new TransferableObject(new TransferableObject.Fetcher() {
089                /**
090                 * This will be called when the transfer data is requested at
091                 * the very end. At this point we can remove the object from its
092                 * original place in the list.
093                 */
094                @Override
095                public E getObject() {
096                    ((javax.swing.DefaultListModel<E>) getModel()).remove(sourceIndex);
097                    return selected;
098                }   // end getObject
099            }); // end fetcher
100
101            // as the name suggests, starts the dragging
102            dragSource.startDrag(event, java.awt.dnd.DragSource.DefaultLinkDrop, transfer, this);
103        } else {
104            //System.out.println( "nothing was selected");
105        }
106    }   // end dragGestureRecognized
107
108    /* ********  D R A G   S O U R C E   L I S T E N E R   M E T H O D S  ******** */
109    @Override
110    public void dragDropEnd(java.awt.dnd.DragSourceDropEvent evt) {   //System.out.println( "DragSourceListener.dragDropEnd" );
111    }   // end dragDropEnd
112
113    @Override
114    public void dragEnter(java.awt.dnd.DragSourceDragEvent evt) {   //System.out.println( "DragSourceListener.dragEnter" );
115    }   // end dragEnter
116
117    @Override
118    public void dragExit(java.awt.dnd.DragSourceEvent evt) {   //System.out.println( "DragSourceListener.dragExit" );
119    }   // end dragExit
120
121    @Override
122    public void dragOver(java.awt.dnd.DragSourceDragEvent evt) {   //System.out.println( "DragSourceListener.dragOver" );
123    }   // end dragOver
124
125    @Override
126    public void dropActionChanged(java.awt.dnd.DragSourceDragEvent evt) {   //System.out.println( "DragSourceListener.dropActionChanged" );
127    }   // end dropActionChanged
128
129    /* ********  D R O P   T A R G E T   L I S T E N E R   M E T H O D S  ******** */
130    @Override
131    public void dragEnter(java.awt.dnd.DropTargetDragEvent evt) {   //System.out.println( "DropTargetListener.dragEnter" );
132        evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_MOVE);
133    }   // end dragEnter
134
135    @Override
136    public void dragExit(java.awt.dnd.DropTargetEvent evt) {   //System.out.println( "DropTargetListener.dragExit" );
137    }   // end dragExit
138
139    @Override
140    public void dragOver(java.awt.dnd.DropTargetDragEvent evt) {   //System.out.println( "DropTargetListener.dragOver" );
141    }   // end dragOver
142
143    @Override
144    public void dropActionChanged(java.awt.dnd.DropTargetDragEvent evt) {   //System.out.println( "DropTargetListener.dropActionChanged" );
145        evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_MOVE);
146    }   // end dropActionChanged
147
148    @SuppressWarnings("unchecked") // DnD starts with a generic Object
149    @Override
150    public void drop(java.awt.dnd.DropTargetDropEvent evt) {   //System.out.println( "DropTargetListener.drop" );
151        java.awt.datatransfer.Transferable transferable = evt.getTransferable();
152
153        // If it's our native TransferableObject, use that
154        if (transferable.isDataFlavorSupported(TransferableObject.DATA_FLAVOR)) {
155            evt.acceptDrop(java.awt.dnd.DnDConstants.ACTION_MOVE);
156            Object obj = null;
157            try {
158                obj = transferable.getTransferData(TransferableObject.DATA_FLAVOR);
159            } catch (java.awt.datatransfer.UnsupportedFlavorException | java.io.IOException e) {
160                log.error("Unable to transfer object", e);
161            }
162
163            if (obj != null) {
164                // See where in the list we dropped the element.
165                int dropIndex = locationToIndex(evt.getLocation());
166                javax.swing.DefaultListModel<E> model = (javax.swing.DefaultListModel<E>) getModel();
167
168                if (dropIndex < 0) {
169                    model.addElement((E) obj);
170                } // Else is it moving down the list?
171                else if (sourceIndex >= 0 && dropIndex > sourceIndex) {
172                    model.add(dropIndex - 1, (E) obj);
173                } else {
174                    model.add(dropIndex, (E) obj);
175                }
176
177            } // end if: we got the object
178            // Else there was a problem getting the object
179            else {
180                evt.rejectDrop();
181            }   // end else: can't get the object
182        } // end if: it's a native TransferableObject
183        // Else we can't handle this
184        else {
185            evt.rejectDrop();
186        }
187    }   // end drop
188
189    private final static Logger log = LoggerFactory.getLogger(DnDList.class);
190
191}   // end class DnDList