How can I retain the selected text when trying to drag/drop text from a JTextField?
Created May 7, 2012
Scott Stanchfield
This is a nasty bug in Swing...
The problem is that the Caret's mouse listener and the DragGesture's mouse listener are watching for the same thing.
The caret considers "press" a change of location. The drag gesture waits for a drag. Of course, this means that any press unselects the text.
The following code will fix the problem.
DragDropCaret - replacement Caret that cooperates with a dragger
import java.awt.Component; import java.awt.Point; import java.awt.dnd.DragGestureEvent; import java.awt.event.MouseEvent; import javax.swing.JComponent; import javax.swing.text.DefaultCaret; public class DragDropCaret extends DefaultCaret { private boolean isDragging = false; public void mouseClicked(MouseEvent e) { if ((getComponent() != null) && getComponent().isEnabled()) { getComponent().requestFocus(); } super.mouseClicked(e); } public void mouseDragged(MouseEvent e) { if (isDragging) { if ((getComponent() != null) && getComponent().isEnabled()) { getComponent().requestFocus(); } moveCaret(e); } } public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) { if (isDragging) isDragging = false; else positionCaret(e); } public void startDrag(DragGestureEvent e) { positionCaret((MouseEvent)e.getTriggerEvent()); isDragging = true; } }
TextDragDropper - Helper class to set up drag/drop for a text field. Needs some work to handle non-text drags...
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; 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.DragGestureRecognizer; 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.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; public class TextDragDropper implements DragGestureListener, DragSourceListener, DropTargetListener { private DragSource dragSource; private DropTarget dropTarget; private DragGestureRecognizer recognizer; private DragDropCaret caret; private JTextComponent textComponent; private boolean isLeftMB = false; private int start = 0; private int end = 0; private boolean isCopy = false; public TextDragDropper(JTextComponent textComponent) { this.textComponent = textComponent; this.textComponent.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { isLeftMB = SwingUtilities.isLeftMouseButton(e); } }); dragSource = new DragSource(); dropTarget = new DropTarget(textComponent, DnDConstants.ACTION_COPY_OR_MOVE, this); recognizer = dragSource.createDefaultDragGestureRecognizer ( textComponent, DnDConstants.ACTION_COPY_OR_MOVE, this ); caret = new DragDropCaret(); textComponent.setCaret(caret); } public void dragDropEnd(DragSourceDropEvent dsde) { if (dsde.getDropSuccess()) { if (start != end) { textComponent.setCaretPosition(start); textComponent.moveCaretPosition(end); } if (!isCopy) textComponent.replaceSelection(""); } } public void dragEnter(DragSourceDragEvent dsde) {} public void dragEnter(DropTargetDragEvent dtde) { if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); else dtde.rejectDrag(); } public void dragExit(DragSourceEvent dse) {} public void dragExit(DropTargetEvent dte) {} public void dragGestureRecognized(DragGestureEvent dge) { if (!isLeftMB) return; int n = textComponent.viewToModel(dge.getDragOrigin()); if (n > textComponent.getSelectionStart() && n <= textComponent.getSelectionEnd()) { String selectedItem = textComponent.getSelectedText(); MouseEvent e = (MouseEvent)dge.getTriggerEvent(); // NOTE: SPECIFIC TO WINDOWS DRAG... CREATES "LINK" // IGNORE IT... // IS THIS OK TO IGNORE FOR OTHER PLATFORMS? NEED TO CHECK if (e.isAltDown()) return; isCopy = e.isControlDown(); dge.startDrag((isCopy ? DragSource.DefaultCopyDrop : DragSource.DefaultMoveDrop), new StringSelection(selectedItem), this); } else { caret.startDrag(dge); } } public void dragOver(DragSourceDragEvent dsde) { } public void dragOver(DropTargetDragEvent dtde) { } public void drop(DropTargetDropEvent dtde) { start = textComponent.getSelectionStart(); end = textComponent.getSelectionEnd(); try { if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) { Transferable tr = dtde.getTransferable(); dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); String s = (String)tr.getTransferData(DataFlavor.stringFlavor); int n = textComponent.viewToModel(dtde.getLocation()); Document doc = textComponent.getDocument(); doc.insertString(n, s, null); dtde.dropComplete(true); } else { dtde.rejectDrop(); } } catch(IOException e) { dtde.rejectDrop(); } catch(UnsupportedFlavorException e) { dtde.rejectDrop(); } catch(BadLocationException e) { dtde.rejectDrop(); } catch(Exception e) { e.printStackTrace(); } } public void dropActionChanged(DragSourceDragEvent dsde) { } public void dropActionChanged(DropTargetDragEvent dtde) { } /** * Gets the textComponent * @return Returns a JTextComponent */ protected JTextComponent getTextComponent() { return textComponent; } /** * Sets the textComponent * @param textComponent The textComponent to set */ protected void setTextComponent(JTextComponent textComponent) { this.textComponent = textComponent; } /** * Gets the start * @return Returns a int */ protected int getStart() { return start; } /** * Sets the start * @param start The start to set */ protected void setStart(int start) { this.start = start; } /** * Gets the recognizer * @return Returns a DragGestureRecognizer */ protected DragGestureRecognizer getRecognizer() { return recognizer; } /** * Sets the recognizer * @param recognizer The recognizer to set */ protected void setRecognizer(DragGestureRecognizer recognizer) { this.recognizer = recognizer; } /** * Gets the isLeftMB * @return Returns a boolean */ protected boolean getIsLeftMB() { return isLeftMB; } /** * Sets the isLeftMB * @param isLeftMB The isLeftMB to set */ protected void setIsLeftMB(boolean isLeftMB) { this.isLeftMB = isLeftMB; } /** * Gets the isCopy * @return Returns a boolean */ protected boolean getIsCopy() { return isCopy; } /** * Sets the isCopy * @param isCopy The isCopy to set */ protected void setIsCopy(boolean isCopy) { this.isCopy = isCopy; } /** * Gets the end * @return Returns a int */ protected int getEnd() { return end; } /** * Sets the end * @param end The end to set */ protected void setEnd(int end) { this.end = end; } /** * Gets the dropTarget * @return Returns a DropTarget */ protected DropTarget getDropTarget() { return dropTarget; } /** * Sets the dropTarget * @param dropTarget The dropTarget to set */ protected void setDropTarget(DropTarget dropTarget) { this.dropTarget = dropTarget; } /** * Gets the dragSource * @return Returns a DragSource */ protected DragSource getDragSource() { return dragSource; } /** * Sets the dragSource * @param dragSource The dragSource to set */ protected void setDragSource(DragSource dragSource) { this.dragSource = dragSource; } /** * Gets the caret * @return Returns a DragDropCaret */ protected DragDropCaret getCaret() { return caret; } /** * Sets the caret * @param caret The caret to set */ protected void setCaret(DragDropCaret caret) { this.caret = caret; } }
Finally, create a subclass of JTextField and add
new TextDragDropper(this);
to its constructors