/* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.util.swing; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import java.io.*; import javax.swing.*; import javax.swing.text.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.util.*; /** * A TransferHandler that we use to handle copying, pasting and DnD operations. * The string handler is heavily inspired by Sun's * DefaultTransferHandler with the main difference being that * we only accept pasting of plain text. We do this in order to avoid html * support problems that appear when pasting formatted text into our editable * area. * * @author Emil Ivov * @author Yana Stamcheva */ public class ExtendedTransferHandler extends TransferHandler { /** * The data flavor used when transferring MetaContacts. */ protected static final DataFlavor metaContactDataFlavor = new DataFlavor(MetaContact.class, "MetaContact"); /** * The drag icon that is the visual representation of the contained * Transferable. */ protected static final ImageIcon dragIcon = UtilActivator.getResources() .getImage("service.gui.icons.DRAG_ICON"); /** * Returns the type of transfer actions supported by the source; * any bitwise-OR combination of COPY, MOVE * and LINK. *

* Some models are not mutable, so a transfer operation of MOVE * should not be advertised in that case. Returning NONE * disables transfers from the component. * * @param c the component holding the data to be transferred; * provided to enable sharing of TransferHandlers * @return {@code COPY} if the transfer property can be found, * otherwise returns NONE */ public int getSourceActions(JComponent c) { return TransferHandler.COPY; } /** * Indicates whether a component will accept an import of the given * set of data flavors prior to actually attempting to import it. We return * true to indicate that the transfer with at least one of the * given flavors would work and false to reject the transfer. *

* @param comp component * @param flavor the data formats available * @return true if the data can be inserted into the component, false * otherwise * @throws NullPointerException if support is {@code null} */ public boolean canImport(JComponent comp, DataFlavor flavor[]) { for (int i = 0, n = flavor.length; i < n; i++) { if (flavor[i].equals(DataFlavor.javaFileListFlavor)) { return true; } else if (flavor[i].equals(DataFlavor.stringFlavor)) { if (comp instanceof JTextComponent) { JTextComponent c = (JTextComponent)comp; if (c.isEditable() && c.isEnabled()) { return true; } } return false; } } return false; } /** * Creates a transferable for text pane components in order to enable drag * and drop of text. * @param component the component for which to create a * Transferable * @return the created Transferable */ public Transferable createTransferable(JComponent component) { if (component instanceof JTextPane || component instanceof JTextField) { return new SelectedTextTransferable((JTextComponent) component); } return super.createTransferable(component); } /** * Handles transport (cut and copy) from the chat panel to * clipboard. This method will only transfer plain text and would * explicitly ignore any formatting. *

* @param comp the component holding the data to be transferred; * provided to enable sharing of TransferHandlers * @param clipboard the clipboard to transfer the data into * @param action the transfer action requested; this should * be a value of either COPY or MOVE; * the operation performed is the intersection of the transfer * capabilities given by getSourceActions and the requested action; * the intersection may result in an action of NONE * if the requested action isn't supported * @throws IllegalStateException if the clipboard is currently unavailable * @see Clipboard#setContents(Transferable, ClipboardOwner) */ public void exportToClipboard(JComponent comp, Clipboard clipboard, int action) throws IllegalStateException { if (comp instanceof JTextComponent) { JTextComponent textComponent = (JTextComponent)comp; int startIndex = textComponent.getSelectionStart(); int endIndex = textComponent.getSelectionEnd(); if (startIndex != endIndex) { try { Document doc = textComponent.getDocument(); String srcData = doc.getText(startIndex, endIndex - startIndex); StringSelection contents = new StringSelection(srcData); // this may throw an IllegalStateException, // but it will be caught and handled in the // action that invoked this method clipboard.setContents(contents, null); if (action == TransferHandler.MOVE) { doc.remove(startIndex, endIndex - startIndex); } } catch (BadLocationException ble) { //we simply ignore } } } } /** * Transferable for text pane components that enables drag and drop of text. */ public class SelectedTextTransferable implements Transferable { private JTextComponent textComponent; /** * Creates an instance of SelectedTextTransferable. * @param component the text component */ public SelectedTextTransferable(JTextComponent component) { this.textComponent = component; } /** * Returns supported flavors. * @return an array of supported flavors */ public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[]{DataFlavor.stringFlavor}; } /** * Returns true if the given flavor is supported, * otherwise returns false. * @param flavor the data flavor to verify * @return true if the given flavor is supported, * otherwise returns false */ public boolean isDataFlavorSupported(DataFlavor flavor) { return DataFlavor.stringFlavor.equals(flavor); } /** * Returns the selected text. * @param flavor the flavor * @return the selected text * @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 (!DataFlavor.stringFlavor.equals(flavor)) { throw new UnsupportedFlavorException(flavor); } return textComponent.getSelectedText(); } } /** * Overrides TransferHandler.getVisualRepresentation(Transferable t) * in order to return a custom drag icon. *

* The default parent implementation of this method returns null. * * @param t the data to be transferred; this value is expected to have been * created by the createTransferable method * @return the icon to show when dragging */ public Icon getVisualRepresentation(Transferable t) { Icon icon = null; if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) { String text = null; try { text = (String) t.getTransferData(DataFlavor.stringFlavor); } catch (UnsupportedFlavorException e) {} catch (IOException e) {} if (text != null) { Rectangle2D bounds = GuiUtils.getStringBounds(text); BufferedImage image = new BufferedImage( (int) Math.ceil(bounds.getWidth()), (int) Math.ceil(bounds.getHeight()), BufferedImage.TYPE_INT_ARGB); Graphics g = image.getGraphics(); AntialiasingManager.activateAntialiasing(g); g.setColor(Color.BLACK); // Don't know why if we draw the string on y = 0 it doesn't // appear in the visible area. g.drawString(text, 0, 10); icon = new ImageIcon(image); } } return icon; } // Patch for bug 4816922 "No way to set drag icon: // TransferHandler.getVisualRepresentation() is not used". // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4816922 // The following workaround comes from bug comments section! private static SwingDragGestureRecognizer recognizer = null; private static class SwingDragGestureRecognizer extends DragGestureRecognizer { SwingDragGestureRecognizer(DragGestureListener dgl) { super(DragSource.getDefaultDragSource(), null, NONE, dgl); } void gestured(JComponent c, MouseEvent e, int srcActions, int action) { setComponent(c); setSourceActions(srcActions); appendEvent(e); fireDragGestureRecognized(action, e.getPoint()); } /** * Registers this DragGestureRecognizer's Listeners with the Component. */ protected void registerListeners() {} /** * Unregister this DragGestureRecognizer's Listeners with the Component. *

* Subclasses must override this method. */ protected void unregisterListeners() {} } /** * Overrides TransferHandler.exportAsDrag method in order to call * our own SwingDragGestureRecognizer, which takes care of the * visual representation icon. * * @param comp the component holding the data to be transferred; this * argument is provided to enable sharing of TransferHandlers * by multiple components * @param e the event that triggered the transfer * @param action the transfer action initially requested; this should * be a value of either COPY or MOVE; * the value may be changed during the course of the drag operation */ public void exportAsDrag(JComponent comp, InputEvent e, int action) { int srcActions = getSourceActions(comp); int dragAction = srcActions & action; // only mouse events supported for drag operations if (! (e instanceof MouseEvent)) action = NONE; if (action != NONE && !GraphicsEnvironment.isHeadless()) { if (recognizer == null) { recognizer = new SwingDragGestureRecognizer(new DragHandler()); } recognizer.gestured(comp, (MouseEvent) e, srcActions, dragAction); } else { exportDone(comp, null, NONE); } } /** * This is the default drag handler for drag and drop operations that * use the TransferHandler. */ private static class DragHandler implements DragGestureListener, DragSourceListener { private boolean scrolls; // --- DragGestureListener methods ----------------------------------- /** * A Drag gesture has been recognized. * @param dge the DragGestureEvent that notified us */ public void dragGestureRecognized(DragGestureEvent dge) { JComponent c = (JComponent) dge.getComponent(); ExtendedTransferHandler th = (ExtendedTransferHandler) c.getTransferHandler(); Transferable t = th.createTransferable(c); if (t != null) { scrolls = c.getAutoscrolls(); c.setAutoscrolls(false); try { Image img = null; Icon icn = th.getVisualRepresentation(t); if (icn != null) { if (icn instanceof ImageIcon) { img = ((ImageIcon) icn).getImage(); } else { img = new BufferedImage(icn.getIconWidth(), icn.getIconHeight(), BufferedImage.TYPE_4BYTE_ABGR); Graphics g = img.getGraphics(); icn.paintIcon(c, g, 0, 0); } } if (img == null) { dge.startDrag(null, t, this); } else { dge.startDrag(null, img, new Point(0, -1 * img.getHeight(null)), t, this); } return; } catch (RuntimeException re) { c.setAutoscrolls(scrolls); } } th.exportDone(c, t, NONE); } // --- DragSourceListener methods ----------------------------------- /** * As the hotspot enters a platform dependent drop site. * @param e the DragSourceDragEvent containing the details of * the drag */ public void dragEnter(DragSourceDragEvent e) {} /** * As the hotspot moves over a platform dependent drop site. * @param e the DragSourceDragEvent containing the details of * the drag */ public void dragOver(DragSourceDragEvent e) { } /** * As the hotspot exits a platform dependent drop site. * @param e the DragSourceDragEvent containing the details of * the drag */ public void dragExit(DragSourceEvent e) {} /** * As the operation completes. * @param e the DragSourceDragEvent containing the details of * the drag */ public void dragDropEnd(DragSourceDropEvent e) { DragSourceContext dsc = e.getDragSourceContext(); JComponent c = (JComponent) dsc.getComponent(); if (e.getDropSuccess()) { ((ExtendedTransferHandler) c.getTransferHandler()) .exportDone(c, dsc.getTransferable(), e.getDropAction()); } else { ((ExtendedTransferHandler) c.getTransferHandler()) .exportDone(c, dsc.getTransferable(), NONE); } c.setAutoscrolls(scrolls); } public void dropActionChanged(DragSourceDragEvent dsde) {} } }