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