mirror of
https://github.com/SuspiciousActivity/ByteEdit.git
synced 2026-08-13 17:56:17 +00:00
457 lines
14 KiB
Java
457 lines
14 KiB
Java
/*
|
|
* 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 <code>RTextArea</code> via
|
|
* drag-and-drop. This class is pretty much ripped off from a subclass of
|
|
* <code>BasicTextUI</code>. In the future, it will include the ability to
|
|
* drag-and-drop files into <code>RTextArea</code>s (i.e., the text will be
|
|
* inserted into the text area).
|
|
* <p>
|
|
*
|
|
* 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:
|
|
* <ol>
|
|
* <li>First, an attempt is made to find a flavor matching the content tyep of
|
|
* the EditorKit for the component.
|
|
* <li>Second, an attempt to find a text/plain flavor is made.
|
|
* <li>Third, an attempt to find a flavor representing a String reference in the
|
|
* same VM is made.
|
|
* <li>Lastly, DataFlavor.stringFlavor is searched for.
|
|
* </ol>
|
|
*
|
|
* @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 (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() == 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) 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 transfered. 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 transfered. This argument is
|
|
* provided to enable sharing of TransferHandlers by multiple
|
|
* components.
|
|
* @return The representation of the data to be transfered.
|
|
*
|
|
*/
|
|
@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 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.
|
|
*/
|
|
@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 <code>true</code> 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 <code>true</code> 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");
|
|
}
|
|
}
|
|
}
|
|
}
|