JMU
Cut, Copy and Paste
in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Introduction
Background
Image Cut, Copy and Paste using a Custom TransferHandler
Image CCP using a Custom TransferHandler (cont.)
Forwarding the Focus
javaexamples/cutcopypaste/image/customhandler/ActionForwarder.java
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;

/**
 * An ActionForwarder keeps track of which GUI Component has the
 * focus and forwards actionPerformed messages to the appropriate
 * Component.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class ActionForwarder implements ActionListener, PropertyChangeListener
{
  private JComponent focusOwner;
  
  /**
   * Default Constructor.
   */
  public ActionForwarder()
  {
    focusOwner = null;
  }

  /**
   * Handle propertyChange messages. Specifically, keep track of
   * the Component with the focus.
   * 
   * @param pce  The event that generated the message
   */
  public void propertyChange(PropertyChangeEvent pce) 
  {
    Object source = pce.getNewValue();
    
    if (source instanceof JComponent) 
    {
        focusOwner = (JComponent)source;
    }
    else
    {
        focusOwner = null;
    }
  }

  /**
   * Handle actionPerformed messages. Specifically, forward them to
   * the Component with the focus (if that Component has an
   * appropriate Action).
   * 
   * @param ae The event that generated the message.
   */
  public void actionPerformed(ActionEvent e) 
  {
    if (focusOwner == null) return;
    
    String actionCommand = (String)e.getActionCommand();
    Action action = focusOwner.getActionMap().get(actionCommand);
    if (action != null) 
    {
      action.actionPerformed(new ActionEvent(focusOwner, ActionEvent.ACTION_PERFORMED, null));
    }
  }
}
        
Image CCP using a Custom TransferHandler (cont.)
Setting-Up the ActionForwarder in the Application
javaexamples/cutcopypaste/image/customhandler/ImagePanelDemo.java (Fragment: CCP)
    KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
    ActionForwarder forwarder = new ActionForwarder();
    manager.addPropertyChangeListener("permanentFocusOwner", forwarder);
    
    // Enable the GUI components to indicate when they have the focus
    manager.addPropertyChangeListener("permanentFocusOwner", left);
    manager.addPropertyChangeListener("permanentFocusOwner", right);
        
Image CCP using a Custom TransferHandler (cont.)
Setting-Up the Keyboard Handling
javaexamples/cutcopypaste/image/customhandler/ImagePanel.java (Fragment: CCP)
    setFocusable(true);

    ActionMap map = getActionMap();
    map.put(TransferHandler.getCutAction().getValue(Action.NAME),
            TransferHandler.getCutAction());
    map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
            TransferHandler.getCopyAction());
    map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
            TransferHandler.getPasteAction());
    
    InputMap imap = this.getInputMap();
    imap.put(KeyStroke.getKeyStroke("ctrl X"),
        TransferHandler.getCutAction().getValue(Action.NAME));
    imap.put(KeyStroke.getKeyStroke("ctrl C"),
        TransferHandler.getCopyAction().getValue(Action.NAME));
    imap.put(KeyStroke.getKeyStroke("ctrl V"),
        TransferHandler.getPasteAction().getValue(Action.NAME));
        
Image CCP using a Custom TransferHandler (cont.)
Image CCP using a Custom TransferHandler
Requirements of the GUI Component
javaexamples/cutcopypaste/image/customhandler/ImageTransferer.java
import java.awt.*;

/**
 * The requirements of a Component that can transfer an Image
 * (e.g., using drag-and-drop or cut/copy/paste).
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public interface ImageTransferer
{
  /**
   * Get the current image.
   * 
   * @return The Image
   */
  public abstract Image getImage();
  
  /**
   * Get the source actions supported by this ImageTransferer.
   * 
   * @return TransferHandler.MOVE or TransferHandler.COPY
   */
  public abstract int getSourceActions();

  /**
   * Set the current image.
   * 
   * @param The Image to use
   */
  public abstract void setImage(Image image);
}
        
Image CCP using a Custom TransferHandler
The Transferable
javaexamples/cutcopypaste/image/customhandler/ImageSelection.java
import java.awt.*;
import java.awt.datatransfer.*;

/**
 * A Transferable wrapper for an Image object.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version For use with an ImageTransferHandler
 */
public class ImageSelection implements Transferable
{
  public static final DataFlavor IMAGE_FLAVOR = DataFlavor.imageFlavor;

  private Image image;
  
  /**
   * Construct an ImageSelection.
   * 
   * @param image  The Image to wrap
   */
  public ImageSelection(Image image)
  {
    this.image = image;
  }
  
  /**
   * Get the DataFlavor objects supported by this Transferable.
   * 
   * @return The DataFlavor objects
   */
  public DataFlavor[] getTransferDataFlavors()
  {
    return new DataFlavor[] { DataFlavor.imageFlavor };
  }

  /**
   * Is the given DataFlavor supported by this Transferable?
   * 
   * @param DataFlavor  The DataFlavor of interest
   * @return true if it is supported; false otherwise
   */
  public boolean isDataFlavorSupported(DataFlavor flavor)
  {
    for (DataFlavor supported: getTransferDataFlavors())
    {
      if (flavor.equals(supported)) return true;
    }
    return false;
  }

  /**
   * Get the Transferable Object for a particular DataFlavor.
   * 
   * @param flavor   The DataFlavor of interest
   */
  public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException
  {
    if (!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor);
    
    return image;
  }
}
        
Image CCP using a Custom TransferHandler
The Custom TransferHandler
javaexamples/cutcopypaste/image/customhandler/ImageTransferHandler.java
import java.awt.*;
import java.io.IOException;
import java.awt.datatransfer.*;
import javax.swing.*;

/**
 * A TransferHandler that supports DnD and CCP on Component objects
 * that implement the ImageTransferer interface.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class ImageTransferHandler extends TransferHandler
{
  private static final long serialVersionUID = 1L;

  private boolean         shouldCutWhenDone;
  private ImageTransferer source;
  
  /**
   * Import the Transferable in the TransferSupport into the
   * JComponent in the TransferSupport.
   * 
   * @param support  The TransferSupport
   * @return true if the data was imported; false otherwise
   */
  public boolean importData(TransferHandler.TransferSupport support)
  {
    JComponent c = (JComponent)support.getComponent();
    Transferable t = support.getTransferable();

    shouldCutWhenDone = false;
    
    if ((source != c) && canImport(support)) 
    {
      ImageTransferer component = (ImageTransferer)c;
      try 
      {
        Image image = (Image)t.getTransferData(ImageSelection.IMAGE_FLAVOR);
        component.setImage(image);
        shouldCutWhenDone = true;
        return true;
      } 
      catch (IOException | UnsupportedFlavorException e) 
      {
        return false;
      }
    }
    return false;
  }

  /**
   * Create a Transferable using the contents of the given JComponent.
   * 
   */
  protected Transferable createTransferable(JComponent c) 
  {
    ImageTransferer component = (ImageTransferer)c;
    source = component;
    shouldCutWhenDone = true; // By default
    return new ImageSelection(component.getImage());
  }

  /**
   * Get the source actions supported by this TransferHandler
   * for the given Component.
   * 
   * @param c  The Component of interest
   * @return   The source actions supported (COPY_OR_MOVE)
   */
  public int getSourceActions(JComponent c) 
  {
    return COPY_OR_MOVE;
  }

  /**
   * Complete the export process.
   * 
   * @param c      The Component
   * @param data   The Transferable
   * @param action The action
   */
  protected void exportDone(JComponent c, Transferable data, int action) 
  {
    if ((action == MOVE) && shouldCutWhenDone)
    {
      ImageTransferer component = (ImageTransferer)c;
      component.setImage(null);
    }
    source = null;
  }

  /**
   * Return true if the Component in the TransferSupport can support one of the 
   * DataFlavor objects in the TransferSupport.
   * 
   * @param support  The TransferSupport
   */
  public boolean canImport(TransferHandler.TransferSupport support)
  {
    DataFlavor[] flavors = support.getDataFlavors();
    
    for (DataFlavor flavor: flavors)
    {
      if (flavor.equals(ImageSelection.IMAGE_FLAVOR)) return true;
    }
    return false;
  }
}
        
Image CCP using a Custom TransferHandler
The GUI Component
javaexamples/cutcopypaste/image/customhandler/ImagePanel.java
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;

/**
 * A Component that supports drag-and-drop and cut/copy/paste
 * for Image objects.
 * 
 * @author  Prof. David Bernstein
 * @version Using a custom Transferable and TransferHandler
 */
public class ImagePanel extends JPanel implements ImageTransferer, PropertyChangeListener
{
  private static final long serialVersionUID = 1L;

  private boolean      dragEnabled;
  private Image        image;
  
  /**
   * Default Constructor.
   */
  public ImagePanel(String id)
  {
    super();
    image = null;
    setTransferHandler(new ImageTransferHandler());
    
    setFocusable(true);

    ActionMap map = getActionMap();
    map.put(TransferHandler.getCutAction().getValue(Action.NAME),
            TransferHandler.getCutAction());
    map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
            TransferHandler.getCopyAction());
    map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
            TransferHandler.getPasteAction());
    
    InputMap imap = this.getInputMap();
    imap.put(KeyStroke.getKeyStroke("ctrl X"),
        TransferHandler.getCutAction().getValue(Action.NAME));
    imap.put(KeyStroke.getKeyStroke("ctrl C"),
        TransferHandler.getCopyAction().getValue(Action.NAME));
    imap.put(KeyStroke.getKeyStroke("ctrl V"),
        TransferHandler.getPasteAction().getValue(Action.NAME));

    // Forward mousePressed messages to a TrasnferHandler at
    // the start of a drag
    addMouseListener(
        new MouseAdapter()
        // An anonymous inner class that overrides mousePressed() in MouseAdapter
        {
          public void mousePressed(MouseEvent e) 
          {
            JComponent c = (JComponent)e.getSource();
            TransferHandler handler = c.getTransferHandler();
            handler.exportAsDrag(c, e, TransferHandler.MOVE);
          }
        }
    );
    
  }

  // Methods required by ImageTransferer

  /**
   * Get the Image associated with this ImagePanel.
   * 
   * @return  The Image
   */
  public Image getImage()
  {
    return image;
  }
  
  /**
   * Get the source actions supported by this ImageTransferer.
   * 
   * @return TransferHandler.MOVE or TransferHandler.COPY
   */
  public int getSourceActions()
  {
    return TransferHandler.COPY_OR_MOVE;
  }
  
  /**
   * Set the Image on this Component.
   * 
   * @param image  The Image to use.
   */
  public void setImage(Image image)
  {
    this.image = image;
    repaint();
  }

  
  // Methods that provide the desired GUI functionality

  /**
   * Is drag enabled for this Component?
   * 
   * @return true if enabled; false otherwise
   */
  public boolean getDragEnabled()
  {
    return dragEnabled;
  }

  /**
   * Render this Component.
   * 
   * @param g  The rendering engine to use
   */
  public void paint(Graphics g)
  {
    super.paint(g);
    
    if (image != null)
    {
      int width  = getWidth();
      int height = getHeight();
      int iw = image.getWidth(null);
      int ih = image.getHeight(null);
      
      g.drawImage(image, width/2 - iw/2, height/2 - ih/2, null);
    }
  }
  
  /**
   * Determine whether drag is enabled on this Component.
   * 
   * @param enabled  true to enable; false to disable
   */
  public void setDragEnabled(boolean enabled)
  {
    dragEnabled = enabled;
  }
  
  
  // Methods required by PropertyChangeListener

  /**
   * Handle propertyChange messages. Specifically, keep track of
   * the Component with the focus.
   * 
   * @param pce  The event that generated the message
   */
  public void propertyChange(PropertyChangeEvent pce) 
  {
    Object source = pce.getNewValue();

    if (source == this) setBackground(Color.WHITE);
    else                setBackground(Color.GRAY);
    repaint();
  }
}