All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.ui.rtextarea.RTATextTransferHandler Maven / Gradle / Ivy

/*
 * 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 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 */ @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: *

    *
  1. First, an attempt is made to find a flavor matching the content * tyep of the EditorKit for the component. *
  2. Second, an attempt to find a text/plain flavor is made. *
  3. Third, an attempt to find a flavor representing a String * reference in the same VM is made. *
  4. 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 * @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 (DataFlavor dataFlavor : flavors) { if (dataFlavor.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 (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 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 (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"); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy