JMU
Drag and Drop
in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Introduction
Participants
The Process
Support in Existing Components
An Example with Text Components
An Example with Text Components
javaexamples/draganddrop/outofthebox/OutOfTheBoxDemo.java
import java.awt.*;
import javax.swing.*;

/**
 * A demonstration of drag and drop with text components that
 * support both "out of the box".
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class OutOfTheBoxDemo implements Runnable
{
  /**
   * Create and show the GUI.
   * 
   * @param args The command line arguments (which are ignored)
   * @throws Exception if something goes wrong
   */
  public static void main(String[] args) throws Exception
  {
    SwingUtilities.invokeAndWait(new OutOfTheBoxDemo());
  }

  /**
   * The code to execute in the event dispatch thread.
   */
  public void run()
  {
    JFrame f = new JFrame("DnD Out of the Box");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(640, 480);

    JPanel cp = (JPanel)f.getContentPane();
    cp.setLayout(new BorderLayout());
    cp.add(new JLabel("Drag from the JList; Drop into the JTextArea", JLabel.CENTER),
        BorderLayout.NORTH);
    
    // The source
    String[] data = {"CS149", "CS159", "CS240", "CS261", "CS345", "CS361"};
    JList<String> list = new JList<String>(data);
    list.setBorder(BorderFactory.createTitledBorder("Offered"));
    list.setFixedCellWidth(100);
    list.setDragEnabled(true); // Drag support must be enabled manually
    cp.add(list,  BorderLayout.WEST);
    
    // The target
    JTextArea textArea = new JTextArea(); // Drop support is enabled by default
    textArea.setBorder(BorderFactory.createTitledBorder("Completed"));
    cp.add(textArea, BorderLayout.CENTER);
    
    f.setVisible(true);
  }
}
        
Adding Support to Other Text Components
Adding Support to Other Text Components (cont.)
Adding Support to Other Text Components (cont.)
javaexamples/draganddrop/hooks/LabelDemo.java (Fragment: drop)
    // Use a bean-oriented handler for objects with a text property
    // (i.e., with getText() and setText() methods)
    label.setTransferHandler(new TransferHandler("text"));
        
Adding Support to Other Text Components (cont.)
javaexamples/draganddrop/hooks/LabelDemo.java (Fragment: drag)
    label.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.COPY);
          }
        }
    );
        
Drop Support using the "Standard" TransferHandler
Drop Support using TransferHandler (cont.)
The Positioned String
javaexamples/draganddrop/drop/standardhandler/StringContent.java
import java.awt.*;

/**
 * An encapsulation of a String with a location.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class StringContent
{
  private Point    location;
  private String   text;  

  /**
   * Explicit Value Constructor.
   * 
   * @param s  The String
   * @param x  The x-coordinate
   * @param y  The y-coordinate
   */
  public StringContent(String s, int x, int y)
  {
    this.text = s;
    this.location = new Point(x, y);
  }

  /**
   * Get the location.
   * 
   * @return The location.
   */
  public Point getLocation()
  {
    return location;
  }

  /**
   * Get the text.
   * 
   * @return The text
   */
  public String getText()
  {
    return text;
  }
}        
Drop Support using TransferHandler (cont.)
The GUI Component
javaexamples/draganddrop/drop/standardhandler/StringCollage.java
import java.awt.Graphics;
import java.util.*;
import javax.swing.*;

/**
 * An example of a Component that supports text drag-and-drop.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version Drop Only; Standard Handler
 */
public class StringCollage extends JPanel
{
  private static final long serialVersionUID = 1L;

  private List<StringContent>       elements;
  
  /**
   * Default Constructor.
   */
  public StringCollage()
  {
    super();
    elements = new ArrayList<StringContent>();
    
    // Use a bean-oriented handler for objects with a text property
    // (i.e., with getText() and setText() methods)
    setTransferHandler(new TransferHandler("text"));
  }
  
  // Methods required to be a "text" bean
  
  /**
   * Get the text on this Component.
   * 
   * This method is required so that this is a bean with the text property.
   * 
   * @return The text of the last element
   */
  public String getText()
  {
    return elements.get(elements.size()-1).getText();
  }
  
  /**
   * Add a new piece of text to this Component at a random location.
   * 
   * @param The text (tab delimited)
   */
  public void setText(String s)
  {
    setText(s, (int)(Math.random()*getWidth()), (int)(Math.random()*getHeight()));
    repaint();
  }

  // Methods that provide the desired GUI functionality

  /**
   * Render this Component.
   * 
   * @param g  The rendering engine to use.
   */
  public void paint(Graphics g)
  {
    super.paint(g);
    for (StringContent e: elements)
    {
      g.drawString(e.getText(), e.getLocation().x, e.getLocation().y);
    }
  }

  /**
   * Set text on this element at the given location.
   * 
   * @param text  The text to add
   * @param x     The x-coordinate
   * @param y     The y-coordinate
   */
  public void setText(String text, int x, int y)
  {
    elements.add(new StringContent(text, x, y));
  }
}
        
Drop Support using TransferHandler (cont.)
An Application
javaexamples/draganddrop/drop/standardhandler/StringCollageDemo.java
import java.awt.*;
import javax.swing.*;

/**
 * A demonstration of a custom Component that supports
 * text drop.
 * 
 * @author  Prof. David Bernstein, James Madison University
 */
public class StringCollageDemo implements Runnable
{
  /**
   * Create and show the GUI.
   * 
   * @param args The command line arguments (which are ignored)
   * @throws Exception if something goes wrong
   */
  public static void main(String[] args) throws Exception
  {
    SwingUtilities.invokeAndWait(new StringCollageDemo());
  }
 
  /**
   * The code to execute in the event dispatch thread.
   */
  public void run()
  {
    JFrame f = new JFrame("TextCollageDemo");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(640, 480);
    
    JPanel cp = (JPanel)f.getContentPane();
    cp.setLayout(new BorderLayout());

    JTextField field = new JTextField();
    field.setBorder(BorderFactory.createTitledBorder("Type text here and then drag it onto the collage"));
    field.setDragEnabled(true);
    cp.add(field, BorderLayout.NORTH);
    
    StringCollage collage = new StringCollage();
    cp.add(collage, BorderLayout.CENTER);
    
    f.setVisible(true);
  }
}
        
Drop Location Support
Drop Location Support (cont.)
The GUI Component
javaexamples/draganddrop/drop/standardhandler/locationsupport/StringCollage.java
import java.awt.Graphics;
import java.awt.Point;
import java.awt.dnd.*;
import java.util.*;
import javax.swing.*;

/**
 * An example of a Component that supports text drop at a particular
 * location.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version Drop Only; Specific Location
 */
public class StringCollage extends JPanel implements DropTargetListener
{
  private static final long serialVersionUID = 1L;

  private List<StringContent>       elements;
  private Point                     dropLocation;
  
  /**
   * Default Constructor.
   */
  public StringCollage()
  {
    super();
    elements = new ArrayList<StringContent>();
    dropLocation = null;
    
    // Create a DropTarget for this Component (and make the Component
    // the DropTargetListener)
    new DropTarget(this, this);
    
    // Use a bean-oriented handler for objects with a text property
    // (i.e., with getText() and setText() methods)
    setTransferHandler(new TransferHandler("text"));
  }
  
  // Methods required to be a "text" bean
  
  /**
   * Get the text on this Component.
   * 
   * This method is required so that this is a bean with the text property.
   * 
   * @return The text of the last element
   */
  public String getText()
  {
    return elements.get(elements.size()-1).getText();
  }
  
  /**
   * Add a new piece of text to this Component.
   * 
   * If the text was dragged onto this Component it will be added at the drop
   * location. Otherwise, it won't be added. To add a new piece of text manually,
   * use the three-parameter version of this method.
   * 
   * This method is required so that this is a bean with the text property.
   * 
   * @param The text (tab delimited)
   */
  public void setText(String s)
  {
    if (dropLocation != null) setText(s, dropLocation.x, dropLocation.y);
    dropLocation = null;
    repaint();
  }

  // Methods that provide the desired GUI functionality

  /**
   * Render this Component.
   * 
   * @param g  The rendering engine to use.
   */
  public void paint(Graphics g)
  {
    super.paint(g);
    for (StringContent e: elements)
    {
      g.drawString(e.getText(), e.getLocation().x, e.getLocation().y);
    }
  }

  /**
   * Set text on this element at the given location.
   * 
   * @param text  The text to add
   * @param x     The x-coordinate
   * @param y     The y-coordinate
   */
  public void setText(String text, int x, int y)
  {
    elements.add(new StringContent(text, x, y));
  }

  // Methods required by DropTargetListener
  
  /**
   * Handle dragEnter messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dragEnter(DropTargetDragEvent dtde)
  {
    dropLocation = dtde.getLocation();
  }

  /**
   * Handle dragOver messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dragOver(DropTargetDragEvent dtde)
  {
    dropLocation = dtde.getLocation();
  }

  /**
   * Handle dropActionChanged messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dropActionChanged(DropTargetDragEvent dtde)
  {
  }

  /**
   * Handle dragEnter messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dragExit(DropTargetEvent dte)
  {
    dropLocation = null;
  }

  /**
   * Handle drop messages.
   * 
   * @param dtde The event that generated the message
   */
  public void drop(DropTargetDropEvent dtde)
  {
    // Invoke the TransferHandler object's importData() method.
    // The TransferHandler will, in turn, invoke this Component's
    // setText() method.
    TransferHandler.TransferSupport support;
    support = new TransferHandler.TransferSupport(this, dtde.getTransferable());
    getTransferHandler().importData(support);
  }
}
        
Drag Support using TransferHandler
Drag Support using TransferHandler (cont.)
The GUI Component
javaexamples/draganddrop/drag/standardhandler/StringCollage.java (Fragment: constructor)
  /**
   * Default Constructor.
   */
  public StringCollage()
  {
    super();
    elements = new ArrayList<StringContent>();

    // Disable drag by default
    setDragEnabled(false);
    
    // Use a bean-oriented handler for objects with a text property
    // (i.e., with getText() and setText() methods)
    setTransferHandler(new TransferHandler("text"));
    
    // Add a MouseListener that responds to mousePressed messages
    // by exporting them to a TransferHandler
    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.COPY);
          }
        }
    );
  }
        
Adding Move Support using TransferHandler
Move Support using TransferHandler (cont.)
The GUI Component
javaexamples/draganddrop/drag/standardhandler/movesupport/StringCollage.java
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.util.*;
import javax.swing.*;

/**
 * An example of a Component that supports text drag and drop.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version Drag Only (Copy or Move); Standard TransferHandler
 */
public class StringCollage extends JPanel implements DragGestureListener, DragSourceListener
{
  private static final long serialVersionUID = 1L;

  private boolean                   dragEnabled;
  private int                       action;
  private List<StringContent>       elements;
  private DragSource                source;

  /**
   * Default Constructor.
   */
  public StringCollage()
  {
    super();
    elements = new ArrayList<StringContent>();
    this.action = DnDConstants.ACTION_COPY;
    
    // Use a bean-oriented handler for objects with a text property
    // (i.e., with getText() and setText() methods)
    setTransferHandler(new TransferHandler("text"));

    // Construct a DragSource that uses this StringCollage as both its Component
    // and its DragGestureListener
    source = new DragSource();
    source.createDefaultDragGestureRecognizer(this, this.action, this);

    // Disable drag by default
    setDragEnabled(false);
  }

  // Methods required to be a "text" bean

  /**
   * Get the text on this Component.
   * 
   * This method is required so that this is a bean with the text property.
   * 
   * @return The text (tab delimited)
   */
  public String getText()
  {
    return elements.get(elements.size()-1).getText();
  }

  /**
   * Add a new piece of text to this Component at location 0, 0.
   * 
   * This method is required so that this is a bean with the text property.
   * 
   * @param The text (tab delimited)
   */
  public void setText(String s)
  {
    setText(s, 0, 0);
  }

  // Methods that provide the desired GUI functionality

  /**
   * Render this Component.
   * 
   * @param g  The rendering engine to use.
   */
  public void paint(Graphics g)
  {
    super.paint(g);
    for (StringContent e: elements)
    {
      g.drawString(e.getText(), e.getLocation().x, e.getLocation().y);
    }
  }

  /**
   * Set text on this element at the given location.
   * 
   * @param text  The text to add
   * @param x     The x-coordinate
   * @param y     The y-coordinate
   */
  public void setText(String text, int x, int y)
  {
    elements.add(new StringContent(text, x, y));
  }


  // Useful additional functionality

  /**
   * Get the DnD action associated with this Component;
   *  
   * @return  The action
   */
  public int getAction()
  {
    return action;
  }

  /**
   * Is drag enabled on this Component?
   * 
   * @return true or false
   */
  public boolean getDragEnabled()
  {
    return dragEnabled;
  }
  
  /**
   * Set the DnD action associated with this Component.
   *  
   * @param  The action (DnDConstants.ACTION_COPY or DnDConstants.ACTION_MOVE)
   */
  public void setAction(int action)
  {
    if (action == DnDConstants.ACTION_MOVE) this.action = DnDConstants.ACTION_MOVE;
    else                                    this.action = DnDConstants.ACTION_COPY;
  }
  
  /**
   * Determine whether or not drag is enabled on this Component.
   * 
   * @param enabled true to enable; false to disable
   */
  public void setDragEnabled(boolean enabled)
  {
    dragEnabled = enabled;
  }
  
  // Required by DragSourceListener

  /**
   * Handle dragEnter messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragEnter(DragSourceDragEvent dsde)
  {
  }

  /**
   * Handle dragOver messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragOver(DragSourceDragEvent dsde)
  {
  }

  /**
   * Handle dropActionChanged messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dropActionChanged(DragSourceDragEvent dsde)
  {
  }

  /**
   * Handle dragExit messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragExit(DragSourceEvent dse)
  {
  }

  /**
   * Handle dragDropEnd messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragDropEnd(DragSourceDropEvent dsde)
  {
    if (action == DnDConstants.ACTION_MOVE)
    {
      elements.remove(elements.size()-1);
      repaint();
    }
  }

  // Methods required by DragGestureListener

  /**
   * Handle dragGestureRecognized messages.
   * 
   * @param dge  The event that generated the message
   */
  public void dragGestureRecognized(DragGestureEvent dge)
  {
    Cursor cursor = DragSource.DefaultMoveDrop;
    
    // StringSelection is a Transferable wrapper for a String
    source.startDrag(dge, cursor, new StringSelection(getText()), this);       
  }
}
        
Move Support with a Custom TransferHandler
Move Support using a Custom TransferHandler (cont.)
The StringTransferHandler
javaexamples/draganddrop/drag/customhandler/StringTransferHandler.java
import java.io.IOException;
import java.awt.datatransfer.*;
import javax.swing.*;

/**
 * A TransferHandler that supports DnD (both Copy and Move) of String objects.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class StringTransferHandler extends TransferHandler
{
  private static final long serialVersionUID = 1L;

  private boolean         shouldCutWhenDone;
  private StringCollage   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)) 
    {
      StringCollage component = (StringCollage)c;
      try 
      {
        String text = (String)t.getTransferData(DataFlavor.stringFlavor);
        component.setText(text);
        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) 
  {
    StringCollage component = (StringCollage)c;
    
    source = component;
    shouldCutWhenDone = true; // By default
    return new StringSelection(component.getText());
  }

  /**
   * 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) 
  {
    StringCollage component = (StringCollage)c;
    
    source = component;
    return component.getAction();
  }

  /**
   * 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)
    {
      StringCollage component = (StringCollage)c;
      component.setText(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(DataFlavor.stringFlavor)) return true;
    }
    return false;
  }
}
        
Move Support using a Custom TransferHandler (cont.)
The GUI Component
javaexamples/draganddrop/drag/customhandler/StringCollage.java (Fragment: constructor)
  /**
   * Default Constructor.
   */
  public StringCollage()
  {
    super();
    elements = new ArrayList<StringContent>();
    this.action = DnDConstants.ACTION_MOVE;

    // Disable drag by default
    setDragEnabled(false);
    
    // Use a custom TransferHandler
    setTransferHandler(new StringTransferHandler());
    
    // Add a MouseListener that responds to mousePressed messages
    // by exporting them to a TransferHandler
    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, action);
          }
        }
    );
  }
        
What's Next?
What's Next? (cont.)
Image Drag-and-Drop using a "Standard" TransferHandler
Image DnD using TransferHandler
The GUI Component
javaexamples/draganddrop/dnd/image/standardhandler/ImagePanel.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * A Component that supports drag-and-drop and cut/copy/paste
 * for Image objects.
 * 
 * @author  Prof. David Bernstein
 * @version DnD (Copy Only); Standard TransferHandler
 */
public class ImagePanel extends JPanel
{
  private static final long serialVersionUID = 1L;

  private boolean      dragEnabled;
  private Image        image;
  
  /**
   * Default Constructor.
   */
  public ImagePanel()
  {
    super();
    image = null;

    setTransferHandler(new TransferHandler("image"));
    
    // Add a MouseListener that will export mousePressed messages
    // to the TransferHandler as drags.
    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.COPY);
          }
        }
    );
  }

  // 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.COPY
   */
  public int getSourceActions()
  {
    return TransferHandler.COPY;
  }
  
  /**
   * 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;
  }
}
        
Image DnD using TransferHandler
An Application
javaexamples/draganddrop/dnd/image/standardhandler/ImagePanelDemo.java
import java.awt.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;

/**
 * A demonstration of a Component that provides drag and drop
 * support for Image objects.
 * 
 * @author  Prof. David Bernstein, James Madison University
 */
public class ImagePanelDemo implements Runnable
{
  /**
   * Construct and display the GUI.
   * 
   * @param args  The command line arguments (which are ignored)
   * @throws Exception If something goes wrong
   */
  public static void main(String[] args) throws Exception
  {
    SwingUtilities.invokeAndWait(new ImagePanelDemo());
  }
  
  /**
   * The code to run in the event dispatch thread.
   */
  public void run()
  {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(640, 480);
    
    JPanel cp = (JPanel)f.getContentPane();
    cp.setLayout(new GridLayout(1,2));
    
    ImagePanel left = new ImagePanel();
    left.setBorder(BorderFactory.createTitledBorder("Left"));
    left.setDragEnabled(true);
    cp.add(left);
    
    ImagePanel right = new ImagePanel();
    right.setBorder(BorderFactory.createTitledBorder("Right"));
    right.setDragEnabled(true);
    cp.add(right);
    
    Image image;
    try
    {
      image = ImageIO.read(new File("WilsonHall.png"));
    }
    catch (IOException ioe)
    {
      image = null;
    }
    left.setImage(image);

    f.setVisible(true);
  }
}
        
Before We Continue
Before We Continue (cont.)
Ways to Create a Transferable Image
Ways to Create a Transferable Image (cont.)
Move Support with TransferHandler
Move Support with TransferHandler (cont.)
The Transferable
javaexamples/draganddrop/dnd/image/standardhandler/movesupport/ImageSelection.java
import java.awt.*;
import java.awt.datatransfer.*;

/**
 * A wrapper for an Image that provides all of the functionality
 * require by the Transferable interface.
 *
 * @author Prof. David Bernstein, James Madison University
 * @version Works with a "standard" TransferHandler
 */
public class ImageSelection implements Transferable
{
  private Image    image;

  private  DataFlavor JVM_LOCAL_OBJECT;
  
  /**
   * Construct a TransferableImage.
   * 
   * @param image  The Image to decorate
   */
  public ImageSelection(Image image)
  {
    this.image = image;
    
    try
    {
      JVM_LOCAL_OBJECT = new DataFlavor("application/x-java-jvm-local-objectref;class=java.awt.image.BufferedImage");
    }
    catch (ClassNotFoundException cnfe)
    {
      JVM_LOCAL_OBJECT = DataFlavor.imageFlavor;
    }
  }

  // Required by the Transferable interface
  
  /**
   * Get the DataFlavor objects supported by this Transferable.
   * 
   * @return The DataFlavor objects
   */
  public DataFlavor[] getTransferDataFlavors()
  {
    return new DataFlavor[] { JVM_LOCAL_OBJECT };
  }

  /**
   * 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;
  }
}
        
Move Support with TransferHandler (cont.)
The GUI Component
javaexamples/draganddrop/dnd/image/standardhandler/movesupport/ImagePanel.java
import java.awt.*;
import java.awt.dnd.*;
import javax.swing.*;

/**
 * A Component that supports drag and drop for Image objects.
 * 
 * @author  Prof. David Bernstein
 * @version DnD (Move Only); Standard TransferHandler
 */
public class ImagePanel extends JPanel implements DropTargetListener, DragGestureListener, DragSourceListener
{
  private static final long serialVersionUID = 1L;

  private boolean       dragEnabled;
  private DragSource    source;
  private Image         image;

  
  /**
   * Default Constructor.
   */
  public ImagePanel()
  {
    image = null;
    
    // Create a DropTarget for this Component.
    // Note: This Component is also the DropTargetListener.
    new DropTarget(this, this);
    
    // Create a DragSource for this Component.
    // Note: This Component is also the DropTargetListener.
    source = new DragSource();
    source.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this);

    
    // Use a bean-oriented handler for objects with an image property
    // (i.e., with getImage() and setImage() methods)
    TransferHandler handler = new TransferHandler("image");
    setTransferHandler(handler);
}

  // Methods required by image beans

  /**
   * Get the Image associated with this ImagePanel.
   * 
   * This method is required so that this Component is a bean
   * with the image property.
   * 
   * @return  The Image
   */
  public Image getImage()
  {
    return image;
  }
  
  /**
   * Set the Image on this Component.
   * 
   * This method is required so that this Component is a bean
   * with the image property.
   * 
   * @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 of DropTargetListener objects
  
  /**
   * Handle dragEnter messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dragEnter(DropTargetDragEvent dtde)
  {
  }

  /**
   * Handle dragOver messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dragOver(DropTargetDragEvent dtde)
  {
  }

  /**
   * Handle dropActionChanged messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dropActionChanged(DropTargetDragEvent dtde)
  {
  }

  /**
   * Handle dragEnter messages.
   * 
   * @param dtde The event that generated the message
   */
  public void dragExit(DropTargetEvent dte)
  {
  }

  /**
   * Handle drop messages.
   * 
   * @param dtde The event that generated the message
   */
  public void drop(DropTargetDropEvent dtde)
  {
    // Invoke the TransferHandler object's importData() method.
    // The TransferHandler will, in turn, invoke this Component's
    // setImage() method.
    TransferHandler.TransferSupport support;
    support = new TransferHandler.TransferSupport(this, dtde.getTransferable());
    getTransferHandler().importData(support);
  }

  // Methods required by DragSourceListener objects
  
  /**
   * Handle dragEnter messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragEnter(DragSourceDragEvent dsde)
  {
  }

  /**
   * Handle dragOver messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragOver(DragSourceDragEvent dsde)
  {
  }
  
  /**
   * Handle dropActionChanged messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dropActionChanged(DragSourceDragEvent dsde)
  {
  }
  
  /**
   * Handle dragExit messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragExit(DragSourceEvent dse) {}
  
  /**
   * Handle dragDropEnd messages.
   * 
   * @param dsde  The event that generated the message
   */
  public void dragDropEnd(DragSourceDropEvent dsde) 
  {
    // Since this Component only supports move actions,
    // delete the Image.
    setImage(null);
  }

  
  // Methods required by DragGestureListener objects
  
  /**
   * Handle dragGestureRecognized messages.
   * 
   * @param dge The event that generated the message 
   */
  public void dragGestureRecognized(DragGestureEvent dge) 
  {
    source.startDrag(dge, DragSource.DefaultMoveDrop, new ImageSelection(image), this);       
  }
}
        
Image DnD with a Custom TransferHandler
Image DnD with a Custom TransferHandler (cont.)
The Requirements of the GUI Component
javaexamples/draganddrop/dnd/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 DnD with a Custom TransferHandler (cont.)
The Transferable
javaexamples/draganddrop/dnd/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 DnD with a Custom TransferHandler (cont.)
The Custom TransferHandler
javaexamples/draganddrop/dnd/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 DnD with a Custom TransferHandler (cont.)
The GUI Component
javaexamples/draganddrop/dnd/image/customhandler/ImagePanel.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * A Component that supports drag-and-drop for Image objects.
 * 
 * @author  Prof. David Bernstein
 * @version DnD (Move or Copy); Custom TransferHandler
 */
public class ImagePanel extends JPanel implements ImageTransferer
{
  private static final long serialVersionUID = 1L;

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

    // Add a MouseListener that will forward mousePressed messages
    // to a TransferHandler as drags
    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
   */
  public int getSourceActions()
  {
    return TransferHandler.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;
  }
}
        
DnD with other Objects
DnD with other Objects (cont.)
The Appoinment Class
javaexamples/draganddrop/dnd/appointment/Appointment.java
import java.awt.datatransfer.*;
import java.util.*;

/**
 * An encapsulation of an Appointment.
 * 
 * Note that this class implements the Transferable interface so there
 * is no reason to also create a wrapper.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Appointment implements Transferable
{
  public static DataFlavor APPOINTMENT_FLAVOR = new DataFlavor(Appointment.class, "Appointment");
  private static DataFlavor[] flavors = {APPOINTMENT_FLAVOR};
  
  private Date    date;
  private String  description;

  /**
   * Construct an Appointment with no description and the 
   * current date/time.
   */
  public Appointment()
  {
    this(new Date(), "");
  }
  
  /**
   * Construct an Appointment with the given description and the 
   * current date/time.
   * 
   * @param description  The description
   */
  public Appointment(String description)
  {
    this(new Date(), description);
  }
  
  /**
   * Construct an Appointment with the given date and description.
   * 
   * @param date         The date
   * @param description  The description
   */
  public Appointment(Date date, String description)
  {
    this.date = date;
    this.description = description;
  }
  
  /**
   * Get the date.
   * 
   * @return The date.
   */
  public Date getDate()
  {
    return date;
  }
  
  /**
   * Get the description.
   * 
   * @return The description.
   */
  public String getDescription()
  {
    return description;
  }
  
  // Methods required by Transferable
  
  /**
   * Get the Object to transfer based on the given DataFlavor.
   * 
   * @return  The Object
   * @throws UnsupportedFlavorException if the DataFlavor isn't supported
   */
  public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException
  {
    if (flavor.equals(Appointment.APPOINTMENT_FLAVOR))
    {
      return this;
    }
    throw new UnsupportedFlavorException(flavor);
  }
  
  /**
   * Get an array of all of the DataFlavor objects that are supported.
   * 
   * @return  The DataFlavor objects
   */
  public DataFlavor[] getTransferDataFlavors()
  {
    return flavors;
  }
  
  /**
   * Is the given DataFlavor supported?
   * 
   * @param flavor  The DataFlavor of interest
   * @return true if supported; false otherwise
   */
  public boolean isDataFlavorSupported(DataFlavor flavor)
  {
    for (int i=0; i<flavors.length; i++)
    {
      if (flavor.equals(flavors[0])) return true;
    }
    return false;
  }
}
        
DnD with other Objects (cont.)
Requirements of the GUI Component
javaexamples/draganddrop/dnd/appointment/AppointmentTransferer.java
/**
 * The requirements of a Component that can transfer an Appointment
 * (e.g., using drag-and-drop or cut/copy/paste).
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public interface AppointmentTransferer
{
  /**
   * Get the current Appointment.
   * 
   * @return The Appointment
   */
  public abstract Appointment getAppointment();
  
  /**
   * Get the source actions supported by this AppointmentTransferer.
   * 
   * @return TransferHandler.MOVE or TransferHandler.COPY
   */
  public abstract int getSourceActions();

  /**
   * Set the current Appointment.
   * 
   * @param The Appointment to use
   */
  public abstract void setAppointment(Appointment appointment);
}
        
DnD with other Objects (cont.)
The Custom TransferHandler
javaexamples/draganddrop/dnd/appointment/AppointmentTransferHandler.java
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 AppointmentTransferHandler extends TransferHandler
{
  private static final long serialVersionUID = 1L;

  private boolean         shouldCutWhenDone;
  private AppointmentTransferer 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)) 
    {
      AppointmentTransferer component = (AppointmentTransferer)c;
      try 
      {
        Appointment appointment = (Appointment)t.getTransferData(Appointment.APPOINTMENT_FLAVOR);
        component.setAppointment(appointment);
        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) 
  {
    AppointmentTransferer component = (AppointmentTransferer)c;
    
    source = component;
    shouldCutWhenDone = true; // By default
    return component.getAppointment();
  }

  /**
   * 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)
    {
      AppointmentTransferer component = (AppointmentTransferer)c;
      component.setAppointment(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(Appointment.APPOINTMENT_FLAVOR)) 
      {
        return true;
      }
    }
    return false;
  }
}

        
DnD with other Objects (cont.)
The GUI Component
javaexamples/draganddrop/dnd/appointment/AppointmentPanel.java
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.*;

/**
 * A GUI Component that supports the drag-and-drop of Appointment objects.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class AppointmentPanel extends JPanel implements AppointmentTransferer
{
  private static final long serialVersionUID = 1L;

  private Appointment appointment;
  private boolean     dragEnabled;
  private JLabel      dateLabel, descriptionLabel;
  
  /**
   * Construct an AppointmentPanel without a title.
   */
  public AppointmentPanel()
  {
    this(null);
  }
  
  /**
   * Construct an AppointmentPanel with a title.
   * 
   * @param title  The title.
   */
  public AppointmentPanel(String title)
  {
    super();
    setLayout(new GridLayout(2,1));
    if (title == null) setBorder(BorderFactory.createLineBorder(Color.BLACK));
    else               setBorder(BorderFactory.createTitledBorder(title));
      
    descriptionLabel = new JLabel();
    descriptionLabel.setBorder(BorderFactory.createTitledBorder("Description"));
    dateLabel = new JLabel();
    dateLabel.setBorder(BorderFactory.createTitledBorder("Date/Time"));
    add(descriptionLabel);
    add(dateLabel);
    
    setDragEnabled(true);
    setTransferHandler(new AppointmentTransferHandler());
    
    // Add a MouseListener that will forward mousePressed messages
    // to a TransferHandler as drags
    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 AppointmentTransferer
  
  /**
   * Get the Appointment on this AppointmentPanel.
   * 
   * @return  The Appointment
   */
  public Appointment getAppointment()
  {
    return appointment;
  }
  
  /**
   * Get the source actions supported by this ImageTransferer.
   * 
   * @return The supported source actions
   */
  public int getSourceActions()
  {
    return TransferHandler.COPY_OR_MOVE;
  }
  
  /**
   * Set the Appointment to display on this AppointmentPanel.
   * 
   * @param appointment  The Appointment
   */
  public void setAppointment(Appointment appointment)
  {
    this.appointment = appointment;
    if (appointment != null)
    {
      descriptionLabel.setText(appointment.getDescription());
      dateLabel.setText(appointment.getDate().toString());
    }
    else
    {
      descriptionLabel.setText("");
      dateLabel.setText("");
    }
  }

  // Other useful methods

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

  /**
   * Set whether or not drag is enabled on this Component.
   * 
   * @param enabled  true to enable; false to disable
   */
  public void setDragEnabled(boolean enabled)
  {
    this.dragEnabled= enabled;
  }
}
        
DnD with other Objects (cont.)
An Application
javaexamples/draganddrop/dnd/appointment/AppointmentPanelDemo.java
import java.awt.*;
import java.util.*;
import javax.swing.*;

/**
 * An application that demonstrates DnD for a custom object.
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class AppointmentPanelDemo implements Runnable
{
  /**
   * The entry point.
   * 
   * @param args The command line arguments (which are ignored)
   */
  public static void main(String[] args) throws Exception
  {
    SwingUtilities.invokeAndWait(new AppointmentPanelDemo());
  }
  
  /**
   * The code to execute in the event dispatch thread.
   */
  public void run()
  {
    String[] rooms = {"CS236", "CS243", "CS246", "CS248", "CS250"};
    
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    f.setSize(640, 480);
    JPanel cp = (JPanel)f.getContentPane();
    cp.setLayout(new GridLayout(1,2));
    
    JPanel left = new JPanel();
    left.setLayout(new GridLayout(3,1));
    {
      AppointmentPanel ap = new AppointmentPanel();
      ap.setAppointment(new Appointment(new Date(), "Database Demo"));
      left.add(ap);
      
      ap = new AppointmentPanel();
      ap.setAppointment(new Appointment(new Date(), "Graphics Demo"));
      left.add(ap);
      
      ap = new AppointmentPanel();
      ap.setAppointment(new Appointment(new Date(), "Robotics Demo"));
      left.add(ap);
    }
    cp.add(left);
    
    JPanel right = new JPanel();
    right.setLayout(new GridLayout(rooms.length, 2));
    {
      for (int i=0; i<rooms.length; i++)
      {
        AppointmentPanel ap = new AppointmentPanel(rooms[i]);
        right.add(ap);
      }
    }
    cp.add(right);
    
    f.setVisible(true);
  }
}
        
What Else is Possible?