org.fife.ui.rtextarea.RTATextTransferHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rsyntaxtextarea Show documentation
Show all versions of rsyntaxtextarea Show documentation
RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.
/*
* 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
* LICENSE 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 RTextArea
s (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
*/
@SuppressWarnings("deprecation")
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:
*
* - First, an attempt is made to find a flavor matching the content
* type of the EditorKit for the component.
*
- Second, an attempt to find a text/plain flavor is made.
*
- Third, an attempt to find a flavor representing a String
* reference in the same VM is made.
*
- Lastly, DataFlavor.stringFlavor is searched for.
*
*
* @param flavors The flavors to check if c will accept them.
* @param c The text component to see whether it will accept any of the
* specified data flavors as input.
*/
protected DataFlavor getImportFlavor(DataFlavor[] flavors,
JTextComponent c) {
DataFlavor refFlavor = null;
DataFlavor stringFlavor = null;
for (DataFlavor flavor : flavors) {
String mime = flavor.getMimeType();
if (mime.startsWith("text/plain")) {
return flavor;
}
else if (refFlavor == null &&
mime.startsWith("application/x-java-jvm-local-objectref") &&
flavor.getRepresentationClass() == String.class) {
refFlavor = flavor;
}
else if (stringFlavor == null &&
flavor.equals(DataFlavor.stringFlavor)) {
stringFlavor = flavor;
}
}
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)
throws IOException {
char[] buff = new char[1024];
int nch;
boolean lastWasCR = false;
int last;
StringBuilder 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 StringBuilder(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;
} // End fo switch (buff[counter]).
} // End of for (int counter = 0; counter < nch; counter++).
if (last < nch) {
if (lastWasCR) {
if (last < (nch - 1)) {
sbuff.append(buff, last, nch - last - 1);
}
}
else {
sbuff.append(buff, last, nch - last);
}
}
} // End of while ((nch = in.read(buff, 0, buff.length)) != -1).
if (withinSameComponent) {
((RTextArea)c).beginAtomicEdit();
}
if (lastWasCR) {
sbuff.append('\n');
}
c.replaceSelection(sbuff != null ? sbuff.toString() : "");
}
/**
* 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 transferred. This
* argument is provided to enable sharing of TransferHandlers by
* multiple components.
* @return If the text component is editable, COPY_OR_MOVE is returned,
* otherwise just COPY is allowed.
*/
@Override
public int getSourceActions(JComponent c) {
if (((JTextComponent)c).isEditable()) {
return COPY_OR_MOVE;
}
else {
return COPY;
}
}
/**
* Create a Transferable to use as the source for a data transfer.
*
* @param comp The component holding the data to be transferred. This
* argument is provided to enable sharing of TransferHandlers by
* multiple components.
* @return The representation of the data to be transferred.
*
*/
@Override
protected Transferable createTransferable(JComponent comp) {
exportComp = (JTextComponent)comp;
shouldRemove = true;
p0 = exportComp.getSelectionStart();
p1 = exportComp.getSelectionEnd();
return (p0 != p1) ? new TextTransferable(exportComp, p0, p1) : null;
}
/**
* This method is called after data has been exported. This method should
* remove the data that was transferred 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 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 | IOException e) {
e.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
* @throws UnsupportedFlavorException if the requested data flavor is
* not supported.
* @see DataFlavor#getRepresentationClass
*/
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
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 the specified data flavor is supported for
* this object.
*
* @param flavor the requested flavor for the data
* @return boolean indicating whether the data flavor is supported
*/
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
DataFlavor[] flavors = getTransferDataFlavors();
for (DataFlavor dataFlavor : flavors) {
if (dataFlavor.equals(flavor)) {
return true;
}
}
return false;
}
/**
* Returns whether the specified data flavor is a plain flavor that
* is supported.
*
* @param flavor the requested flavor for the data
* @return boolean indicating whether the data flavor is supported
*/
protected boolean isPlainFlavor(DataFlavor flavor) {
DataFlavor[] flavors = plainFlavors;
for (DataFlavor dataFlavor : flavors) {
if (dataFlavor.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 the specified data flavor is a String flavor that
* is supported.
*
* @param flavor the requested flavor for the data
* @return boolean indicating whether the data flavor is supported
*/
protected boolean isStringFlavor(DataFlavor flavor) {
DataFlavor[] flavors = stringFlavors;
for (DataFlavor dataFlavor : flavors) {
if (dataFlavor.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");
}
}
}
}