/*
* @(#)FileTextFieldTransferHandler.java 1.0 September 8, 2007
*
* Copyright (c) 2007 Werner Randelshofer
* Hausmatt 10, CH-6405 Immensee, Switzerland
* All rights reserved.
*
* The copyright of this software is owned by Werner Randelshofer.
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* Werner Randelshofer. For details see accompanying license terms.
*/
package ch.randelshofer.gui.datatransfer;
import java.awt.datatransfer.*;
import java.awt.im.InputContext;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
/**
* The FileTextFieldTransferHandler can be used to add drag and drop
* support for JTextFields, which contain the path to a file.
*
* @author Werner Randelshofer
* @version 1.0 September 8, 2007 Created.
*/
public class FileTextFieldTransferHandler extends TransferHandler {
private boolean shouldRemove;
private JTextComponent exportComp;
private int p0;
private int p1;
/** Creates a new instance. */
public FileTextFieldTransferHandler() {
}
public boolean importData(JComponent comp, Transferable t) {
JTextComponent c = (JTextComponent)comp;
// if we are importing to the same component that we exported from
// then don't actually do anything if the drop location is inside
// the drag location and set shouldRemove to false so that exportDone
// knows not to remove any data
if (c == exportComp && c.getCaretPosition() >= p0 && c.getCaretPosition() <= p1) {
shouldRemove = false;
return true;
}
boolean imported = false;
if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
InputContext ic = c.getInputContext();
if (ic != null) {
ic.endComposition();
}
try {
java.util.List list = (List) t.getTransferData(DataFlavor.javaFileListFlavor);
c.setText(((File) list.get(0)).getPath());
imported = true;
} catch (UnsupportedFlavorException ex) {
// ex.printStackTrace();
} catch (IOException ex) {
// ex.printStackTrace();
}
}
if (! imported) {
DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c);
if (importFlavor != null) {
InputContext ic = c.getInputContext();
if (ic != null) {
ic.endComposition();
}
try {
String text = (String) t.getTransferData(DataFlavor.stringFlavor);
Reader r = importFlavor.getReaderForText(t);
boolean useRead = false;
handleReaderImport(r, c, useRead);
imported = true;
} catch (UnsupportedFlavorException ex) {
// ex.printStackTrace();
} catch (BadLocationException ex) {
// ex.printStackTrace();
} catch (IOException ex) {
// ex.printStackTrace();
}
}
}
return imported;
}
protected Transferable createTransferable(JComponent comp) {
CompositeTransferable t;
JTextComponent c = (JTextComponent)comp;
shouldRemove = true;
p0 = c.getSelectionStart();
p1 = c.getSelectionEnd();
if (p0 != p1) {
t = new CompositeTransferable();
String text = c.getSelectedText();
//LinkedList fileList = new LinkedList();
//fileList.add(new File(text));
//t.add(new JVMLocalObjectTransferable(DataFlavor.javaFileListFlavor, fileList));
t.add(new StringTransferable(text));
t.add(new PlainTextTransferable(text));
} else {
t = null;
}
return t;
}
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
JTextComponent c = (JTextComponent)comp;
if (!(c.isEditable() && c.isEnabled())) {
return false;
}
for (DataFlavor flavor : transferFlavors) {
if (flavor.isFlavorJavaFileListType() ||
flavor.isFlavorTextType()) {
return true;
}
}
return false;
}
/**
* Try to find a flavor that can be used to import a Transferable.
* The set of usable flavors are tried in the following order:
* <ol>
* <li>First, an attempt to find a text/plain flavor is made.
* <li>Second, an attempt to find a flavor representing a String reference
* in the same VM is made.
* <li>Lastly, DataFlavor.stringFlavor is searched for.
* </ol>
*/
protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) {
DataFlavor plainFlavor = null;
DataFlavor refFlavor = null;
DataFlavor stringFlavor = null;
for (int i = 0; i < flavors.length; i++) {
String mime = flavors[i].getMimeType();
if (mime.startsWith("text/plain")) {
return flavors[i];
} else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
&& flavors[i].getRepresentationClass() == java.lang.String.class) {
refFlavor = flavors[i];
} else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
stringFlavor = flavors[i];
}
}
if (refFlavor != null) {
return refFlavor;
} else if (stringFlavor != null) {
return stringFlavor;
}
return null;
}
/**
* Import the given stream data into the text component.
*/
protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead)
throws BadLocationException, IOException {
if (useRead) {
int startPosition = c.getSelectionStart();
int endPosition = c.getSelectionEnd();
int length = endPosition - startPosition;
EditorKit kit = c.getUI().getEditorKit(c);
Document doc = c.getDocument();
if (length > 0) {
doc.remove(startPosition, length);
}
kit.read(in, doc, startPosition);
} else {
char[] buff = new char[1024];
int nch;
boolean lastWasCR = false;
int last;
StringBuffer sbuff = null;
// Read in a block at a time, mapping \r\n to \n, as well as single
// \r to \n.
while ((nch = in.read(buff, 0, buff.length)) != -1) {
if (sbuff == null) {
sbuff = new StringBuffer(nch);
}
last = 0;
for(int counter = 0; counter < nch; counter++) {
switch(buff[counter]) {
case '\r':
if (lastWasCR) {
if (counter == 0) {
sbuff.append('\n');
} else {
buff[counter - 1] = '\n';
}
} else {
lastWasCR = true;
}
break;
case '\n':
if (lastWasCR) {
if (counter > (last + 1)) {
sbuff.append(buff, last, counter - last - 1);
}
// else nothing to do, can skip \r, next write will
// write \n
lastWasCR = false;
last = counter;
}
break;
default:
if (lastWasCR) {
if (counter == 0) {
sbuff.append('\n');
} else {
buff[counter - 1] = '\n';
}
lastWasCR = false;
}
break;
}
}
if (last < nch) {
if (lastWasCR) {
if (last < (nch - 1)) {
sbuff.append(buff, last, nch - last - 1);
}
} else {
sbuff.append(buff, last, nch - last);
}
}
}
if (lastWasCR) {
sbuff.append('\n');
}
System.out.println("FileTextTransferHandler "+c.getSelectionStart()+".."+c.getSelectionEnd());
c.replaceSelection(sbuff != null ? sbuff.toString() : "");
}
}
// --- TransferHandler methods ------------------------------------
/**
* This is the type of transfer actions supported by the source. Some models are
* not mutable, so a transfer operation of COPY only should
* be advertised in that case.
*
* @param c The component holding the data to be transfered. This
* argument is provided to enable sharing of TransferHandlers by
* multiple components.
* @return This is implemented to return NONE if the component is a JPasswordField
* since exporting data via user gestures is not allowed. If the text component is
* editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
*/
public int getSourceActions(JComponent comp) {
JTextComponent c = (JTextComponent) comp;
if (c instanceof JPasswordField &&
c.getClientProperty("JPasswordField.cutCopyAllowed") !=
Boolean.TRUE) {
return NONE;
}
return c.isEditable() ? COPY_OR_MOVE : COPY;
}
/**
* This method is called after data has been exported. This method should remove
* the data that was transfered if the action was MOVE.
*
* @param source The component that was the source of the data.
* @param data The data that was transferred or possibly null
* if the action is <code>NONE</code>.
* @param action The actual action that was performed.
*/
protected void exportDone(JComponent comp, Transferable data, int action) {
JTextComponent c = (JTextComponent)comp;
// only remove the text if shouldRemove has not been set to
// false by importData and only if the action is a move
if (shouldRemove && action == MOVE) {
try {
Document doc = c.getDocument();
doc.remove(p0, p1 - p0);
} catch (BadLocationException e) {
}
}
exportComp = null;
}
}