Package org.fife.ui.rtextarea

Source Code of org.fife.ui.rtextarea.RTATextTransferHandler$TextTransferable

/*
* 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.*;
import java.awt.im.InputContext;
import java.io.InputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringBufferInputStream;
import java.io.StringReader;
import javax.swing.*;
import javax.swing.text.*;


/**
* 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 BadLocationException, 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 (BadLocationException ble) {
        ble.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 {

    Position p0;
    Position p1;
    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.
     */
    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
     */
    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
     */
    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");
      }
    }

  }


}
TOP

Related Classes of org.fife.ui.rtextarea.RTATextTransferHandler$TextTransferable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.