/*
* 07/29/2004
* RTATextTransferHandler.java - Handles the transfer of data to/from an
* RTextArea via drag-and-drop.
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rtextarea;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.im.InputContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringBufferInputStream;
import java.io.StringReader;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
/**
* Handles the transfer of data to/from an RTextArea via
* drag-and-drop. This class is pretty much ripped off from a subclass of
* BasicTextUI. In the future, it will include the ability to
* drag-and-drop files into RTextAreas (i.e., the text will be
* inserted into the text area).
*
* * The main reason this class is kept around is so we can subclass it. * * @author Robert Futrell * @version 0.1 */ public class RTATextTransferHandler extends TransferHandler { private JTextComponent exportComp; private boolean shouldRemove; private int p0; private int p1; private boolean withinSameComponent; /** * Try to find a flavor that can be used to import a Transferable to a specified * text component. The set of usable flavors are tried in the following order: *
NONE.
* @param action The actual action that was performed.
*/
@Override
protected void exportDone(JComponent source, Transferable data, int action) {
// 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) {
TextTransferable t = (TextTransferable) data;
t.removeText();
if (withinSameComponent) {
((RTextArea) source).endAtomicEdit();
withinSameComponent = false;
}
}
exportComp = null;
if (data instanceof TextTransferable) {
ClipboardHistory.get().add(((TextTransferable) data).getPlainData());
}
}
/**
* This method causes a transfer to a component from a clipboard or a DND drop
* operation. The Transferable represents the data to be imported into the
* component.
*
* @param comp The component to receive the transfer. This argument is provided
* to enable sharing of TransferHandlers by multiple components.
* @param t The data to import
* @return true iff the data was inserted into the component.
*/
@Override
public boolean importData(JComponent comp, Transferable t) {
JTextComponent c = (JTextComponent) comp;
withinSameComponent = c == exportComp;
// 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 (withinSameComponent && c.getCaretPosition() >= p0 && c.getCaretPosition() <= p1) {
shouldRemove = false;
return true;
}
boolean imported = false;
DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c);
if (importFlavor != null) {
try {
InputContext ic = c.getInputContext();
if (ic != null) {
ic.endComposition();
}
Reader r = importFlavor.getReaderForText(t);
handleReaderImport(r, c);
imported = true;
} catch (UnsupportedFlavorException ufe) {
ufe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
return imported;
}
/**
* This method indicates if a component would accept an import of the given set
* of data flavors prior to actually attempting to import it.
*
* @param comp The component to receive the transfer. This argument is
* provided to enable sharing of TransferHandlers by multiple
* components.
* @param flavors The data formats available.
* @return true iff the data can be inserted.
*/
@Override
public boolean canImport(JComponent comp, DataFlavor[] flavors) {
JTextComponent c = (JTextComponent) comp;
if (!(c.isEditable() && c.isEnabled())) {
return false;
}
return (getImportFlavor(flavors, c) != null);
}
/**
* A possible implementation of the Transferable interface for RTextAreas.
*/
static class TextTransferable implements Transferable {
private Position p0;
private Position p1;
private JTextComponent c;
protected String plainData;
private static DataFlavor[] stringFlavors;
private static DataFlavor[] plainFlavors;
TextTransferable(JTextComponent c, int start, int end) {
this.c = c;
Document doc = c.getDocument();
try {
p0 = doc.createPosition(start);
p1 = doc.createPosition(end);
plainData = c.getSelectedText();
} catch (BadLocationException ble) {
}
}
/**
* Fetch the data in a text/plain format.
*/
protected String getPlainData() {
return plainData;
}
/**
* Returns an object which represents the data to be transferred. The class of
* the object returned is defined by the representation class of the flavor.
*
* @param flavor the requested flavor for the data
* @see DataFlavor#getRepresentationClass
* @exception IOException if the data is no longer available in
* the requested flavor.
* @exception UnsupportedFlavorException if the requested data flavor is not
* supported.
*/
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (isPlainFlavor(flavor)) {
String data = getPlainData();
data = (data == null) ? "" : data;
if (String.class.equals(flavor.getRepresentationClass())) {
return data;
} else if (Reader.class.equals(flavor.getRepresentationClass())) {
return new StringReader(data);
} else if (InputStream.class.equals(flavor.getRepresentationClass())) {
return new StringBufferInputStream(data);
}
// fall through to unsupported
} else if (isStringFlavor(flavor)) {
String data = getPlainData();
data = (data == null) ? "" : data;
return data;
}
throw new UnsupportedFlavorException(flavor);
}
/**
* Returns an array of DataFlavor objects indicating the flavors the data can be
* provided in. The array should be ordered according to preference for
* providing the data (from most richly descriptive to least descriptive).
*
* @return an array of data flavors in which this data can be transferred
*/
@Override
public DataFlavor[] getTransferDataFlavors() {
int plainCount = (isPlainSupported()) ? plainFlavors.length : 0;
int stringCount = (isPlainSupported()) ? stringFlavors.length : 0;
int totalCount = plainCount + stringCount;
DataFlavor[] flavors = new DataFlavor[totalCount];
// fill in the array
int pos = 0;
if (plainCount > 0) {
System.arraycopy(plainFlavors, 0, flavors, pos, plainCount);
pos += plainCount;
}
if (stringCount > 0) {
System.arraycopy(stringFlavors, 0, flavors, pos, stringCount);
// pos += stringCount;
}
return flavors;
}
/**
* Returns whether or not the specified data flavor is supported for this
* object.
*
* @param flavor the requested flavor for the data
* @return boolean indicating whether or not the data flavor is supported
*/
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
DataFlavor[] flavors = getTransferDataFlavors();
for (int i = 0; i < flavors.length; i++) {
if (flavors[i].equals(flavor)) {
return true;
}
}
return false;
}
/**
* Returns whether or not the specified data flavor is an plain flavor that is
* supported.
*
* @param flavor the requested flavor for the data
* @return boolean indicating whether or not the data flavor is supported
*/
protected boolean isPlainFlavor(DataFlavor flavor) {
DataFlavor[] flavors = plainFlavors;
for (int i = 0; i < flavors.length; i++) {
if (flavors[i].equals(flavor)) {
return true;
}
}
return false;
}
/**
* Should the plain text flavors be offered? If so, the method getPlainData
* should be implemented to provide something reasonable.
*/
protected boolean isPlainSupported() {
return plainData != null;
}
/**
* Returns whether or not the specified data flavor is a String flavor that is
* supported.
*
* @param flavor the requested flavor for the data
* @return boolean indicating whether or not the data flavor is supported
*/
protected boolean isStringFlavor(DataFlavor flavor) {
DataFlavor[] flavors = stringFlavors;
for (int i = 0; i < flavors.length; i++) {
if (flavors[i].equals(flavor)) {
return true;
}
}
return false;
}
void removeText() {
if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) {
try {
Document doc = c.getDocument();
doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset());
} catch (BadLocationException e) {
}
}
}
// Initialization of supported flavors.
static {
try {
plainFlavors = new DataFlavor[3];
plainFlavors[0] = new DataFlavor("text/plain;class=java.lang.String");
plainFlavors[1] = new DataFlavor("text/plain;class=java.io.Reader");
plainFlavors[2] = new DataFlavor("text/plain;charset=unicode;class=java.io.InputStream");
stringFlavors = new DataFlavor[2];
stringFlavors[0] = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=java.lang.String");
stringFlavors[1] = DataFlavor.stringFlavor;
} catch (ClassNotFoundException cle) {
System.err.println("Error initializing org.fife.ui.RTATextTransferHandler");
}
}
}
}