ScribblePane allows individual PolyLine lines to be selected, cut, copied, pasted, dragged, and dropped : Drag Drop « Swing « Java Tutorial

 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit

import java.awt.AWTEvent;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;

 * This rewrite of ScribblePane allows individual PolyLine lines to be selected,
 * cut, copied, pasted, dragged, and dropped.
public class TransferableScribblePane extends JComponent {
  List lines; // The PolyLines that comprise this scribble

  PolyLine currentLine; // The line currently being drawn

  PolyLine selectedLine; // The line that is current selected

  boolean canDragImage; // Can we drag an image of the line?

  // Lines are 3 pixels wide, and the selected line is drawn dashed
  static Stroke stroke = new BasicStroke(3.0f);

  static Stroke selectedStroke = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,
      0f, new float[] { 3f, 3f, }, 0f);

  // Different borders indicate receptivity to drops
  static Border normalBorder = new LineBorder(, 3);

  static Border canDropBorder = new BevelBorder(BevelBorder.LOWERED);
  public static void main(String args[]) {
    JFrame f = new JFrame("ColorDrag");
    f.getContentPane().setLayout(new FlowLayout());
    f.getContentPane().add(new TransferableScribblePane());
    f.getContentPane().add(new TransferableScribblePane());
  // The constructor method
  public TransferableScribblePane() {
    setPreferredSize(new Dimension(450, 200)); // We need a default size
    setBorder(normalBorder); // and a border.
    lines = new ArrayList(); // Start with an empty list of lines

    // Register interest in mouse button and mouse motion events.

    // Enable drag-and-drop by specifying a listener that will be
    // notified when a drag begins. dragGestureListener is defined later.
    DragSource dragSource = DragSource.getDefaultDragSource();
    dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE,

    // Enable drops on this component by registering a listener to
    // be notified when something is dragged or dropped over us.
    this.setDropTarget(new DropTarget(this, dropTargetListener));

    // Check whether the system allows us to drag an image of the line
    canDragImage = dragSource.isDragImageSupported();

  /** We override this method to draw ourselves. */
  public void paintComponent(Graphics g) {
    // Let the superclass do its painting first

    // Make a copy of the Graphics context so we can modify it
    Graphics2D g2 = (Graphics2D) (g.create());

    // Our superclass doesn't paint the background, so do this ourselves.
    g2.fillRect(0, 0, getWidth(), getHeight());

    // Set the line width and color to use for the foreground

    // Now loop through the PolyLine shapes and draw them all
    int numlines = lines.size();
    for (int i = 0; i < numlines; i++) {
      PolyLine line = (PolyLine) lines.get(i);
      if (line == selectedLine) { // If it is the selected line
        g2.setStroke(selectedStroke); // Set dash pattern
        g2.draw(line); // Draw the line
        g2.setStroke(stroke); // Revert to solid lines
      } else
        g2.draw(line); // Otherwise just draw the line

   * This method is called on mouse button events. It begins a new line or tries
   * to select an existing line.
  public void processMouseEvent(MouseEvent e) {
    if (e.getButton() == MouseEvent.BUTTON1) { // Left mouse button
      if (e.getID() == MouseEvent.MOUSE_PRESSED) { // Pressed down
        if (e.isShiftDown()) { // with Shift key
          // If the shift key is down, try to select a line
          int x = e.getX();
          int y = e.getY();

          // Loop through the lines checking to see if we hit one
          PolyLine selection = null;
          int numlines = lines.size();
          for (int i = 0; i < numlines; i++) {
            PolyLine line = (PolyLine) lines.get(i);
            if (line.intersects(x - 2, y - 2, 4, 4)) {
              selection = line;
          // If we found an intersecting line, save it and repaint
          if (selection != selectedLine) { // If selection changed
            selectedLine = selection; // remember which is selected
            repaint(); // will make selection dashed
        } else if (!e.isControlDown()) { // no shift key or ctrl key
          // Start a new line on mouse down without shift or ctrl
          currentLine = new PolyLine(e.getX(), e.getY());
      } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {// Left Button Up
        // End the line on mouse up
        if (currentLine != null) {
          currentLine = null;

    // The superclass method dispatches to registered event listeners

   * This method is called for mouse motion events. We don't have to detect
   * gestures that initiate a drag in this method. That is the job of the
   * DragGestureRecognizer we created in the constructor: it will notify the
   * DragGestureListener defined below.
  public void processMouseMotionEvent(MouseEvent e) {
    if (e.getID() == MouseEvent.MOUSE_DRAGGED && // If we're dragging
        currentLine != null) { // and a line exists
      currentLine.addSegment(e.getX(), e.getY()); // Add a line segment
      e.consume(); // Eat the event
      repaint(); // Redisplay all lines
    super.processMouseMotionEvent(e); // Invoke any listeners

  /** Copy the selected line to the clipboard, then delete it */
  public void cut() {
    if (selectedLine == null)
      return; // Only works if a line is selected
    copy(); // Do a Copy operation...
    lines.remove(selectedLine); // and then erase the selected line
    selectedLine = null;
    repaint(); // Repaint because a line was removed

  /** Copy the selected line to the clipboard */
  public void copy() {
    if (selectedLine == null)
      return; // Only works if a line is selected
    // Get the system Clipboard object.
    Clipboard c = this.getToolkit().getSystemClipboard();

    // Wrap the selected line in a TransferablePolyLine object
    // and pass it to the clipboard, with an object to receive notification
    // when some other application takes ownership of the clipboard
    c.setContents(new TransferablePolyLine((PolyLine) selectedLine.clone()), new ClipboardOwner() {
      public void lostOwnership(Clipboard c, Transferable t) {
        // This method is called when something else
        // is copied to the clipboard. We could use it
        // to deselect the selected line, if we wanted.

  /** Get a PolyLine from the clipboard, if one exists, and display it */
  public void paste() {
    // Get the system Clipboard and ask for its Transferable contents
    Clipboard c = this.getToolkit().getSystemClipboard();
    Transferable t = c.getContents(this);

    // See if we can extract a PolyLine from the Transferable object
    PolyLine line;
    try {
      line = (PolyLine) t.getTransferData(TransferablePolyLine.FLAVOR);
    } catch (Exception e) { // UnsupportedFlavorException or IOException
      // If we get here, the clipboard doesn't hold a PolyLine we can use
      getToolkit().beep(); // So beep to indicate the error

    lines.add(line); // We got a line from the clipboard, so add it to list
    repaint(); // And repaint to make the line appear

  /** Erase all lines and repaint. */
  public void clear() {

   * This DragGestureListener is notified when the user initiates a drag. We
   * passed it to the DragGestureRecognizer we created in the constructor.
  public DragGestureListener dragGestureListener = new DragGestureListener() {
    public void dragGestureRecognized(DragGestureEvent e) {
      // Don't start a drag if there isn't a selected line
      if (selectedLine == null)

      // Find out where the drag began
      MouseEvent trigger = (MouseEvent) e.getTriggerEvent();
      int x = trigger.getX();
      int y = trigger.getY();

      // Don't do anything if the drag was not near the selected line
      if (!selectedLine.intersects(x - 4, y - 4, 8, 8))

      // Make a copy of the selected line, adjust the copy so that
      // the point under the mouse is (0,0), and wrap the copy in a
      // Tranferable wrapper.
      PolyLine copy = (PolyLine) selectedLine.clone();
      copy.translate(-x, -y);
      Transferable t = new TransferablePolyLine(copy);

      // If the system allows custom images to be dragged, make
      // an image of the line on a transparent background
      Image dragImage = null;
      Point hotspot = null;
      if (canDragImage) {
        Rectangle box = copy.getBounds();
        dragImage = createImage(box.width, box.height);
        Graphics2D g = (Graphics2D) dragImage.getGraphics();
        g.setColor(new Color(0, 0, 0, 0)); // transparent bg
        g.fillRect(0, 0, box.width, box.height);
        g.translate(-box.x, -box.y);
        hotspot = new Point(-box.x, -box.y);


      // Now begin dragging the line, specifying the listener
      // object to receive notifications about the progress of
      // the operation. Note: the startDrag() method is defined by
      // the event object, which is unusual.
      e.startDrag(null, // Use default drag-and-drop cursors
          dragImage, // Use the image, if supported
          hotspot, // Ditto for the image hotspot
          t, // Drag this object
          dragSourceListener); // Send notifications here

   * If this component is the source of a drag, then this DragSourceListener
   * will receive notifications about the progress of the drag. The only one we
   * use here is dragDropEnd() which is called after a drop occurs. We could use
   * the other methods to change cursors or perform other "drag over effects"
  public DragSourceListener dragSourceListener = new DragSourceListener() {
    // Invoked when dragging stops
    public void dragDropEnd(DragSourceDropEvent e) {
      if (!e.getDropSuccess())
        return; // Ignore failed drops
      // If the drop was a move, then delete the selected line
      if (e.getDropAction() == DnDConstants.ACTION_MOVE) {
        selectedLine = null;

    // The following methods are unused here. We could implement them
    // to change custom cursors or perform other "drag over effects".
    public void dragEnter(DragSourceDragEvent e) {

    public void dragExit(DragSourceEvent e) {

    public void dragOver(DragSourceDragEvent e) {

    public void dropActionChanged(DragSourceDragEvent e) {

   * This DropTargetListener is notified when something is dragged over this
   * component.
  public DropTargetListener dropTargetListener = new DropTargetListener() {
    // This method is called when something is dragged over us.
    // If we understand what is being dragged, then tell the system
    // we can accept it, and change our border to provide extra
    // "drag under" visual feedback to the user to indicate our
    // receptivity to a drop.
    public void dragEnter(DropTargetDragEvent e) {
      if (e.isDataFlavorSupported(TransferablePolyLine.FLAVOR)) {

    // Revert to our normal border if the drag moves off us.
    public void dragExit(DropTargetEvent e) {

    // This method is called when something is dropped on us.
    public void drop(DropTargetDropEvent e) {
      // If a PolyLine is dropped, accept either a COPY or a MOVE
      if (e.isDataFlavorSupported(TransferablePolyLine.FLAVOR))
      else { // Otherwise, reject the drop and return

      // Get the dropped object and extract a PolyLine from it
      Transferable t = e.getTransferable();
      PolyLine line;
      try {
        line = (PolyLine) t.getTransferData(TransferablePolyLine.FLAVOR);
      } catch (Exception ex) { // UnsupportedFlavor or IOException
        getToolkit().beep(); // Something went wrong, so beep
        e.dropComplete(false); // Tell the system we failed

      // Figure out where the drop occurred, and translate so the
      // point that was formerly (0,0) is now at that point.
      Point p = e.getLocation();
      line.translate((float) p.getX(), (float) p.getY());

      // Add the line to our list, and repaint

      // Tell the system that we successfully completed the transfer.
      // This means it is safe for the initiating component to delete
      // its copy of the line

    // We could provide additional drag under effects with this method.
    public void dragOver(DropTargetDragEvent e) {

    // If we used custom cursors, we would update them here.
    public void dropActionChanged(DropTargetDragEvent e) {

 * This Shape implementation represents a series of connected line segments. It
 * is like a Polygon, but is not closed. This class is used by the ScribblePane
 * class of the GUI chapter. It implements the Cloneable and Externalizable
 * interfaces so it can be used in the Drag-and-Drop examples in the Data
 * Transfer chapter.
class PolyLine implements Shape, Cloneable, Externalizable {
  float x0, y0; // The starting point of the polyline.

  float[] coords; // The x and y coordinates of the end point of each line

  // segment packed into a single array for simplicity:
  // [x1,y1,x2,y2,...] Note that these are relative to x0,y0
  int numsegs; // How many line segments in this PolyLine

  // Coordinates of our bounding box, relative to (x0, y0);
  float xmin = 0f, xmax = 0f, ymin = 0f, ymax = 0f;

  // No arg constructor assumes an origin of (0,0)
  // A no-arg constructor is required for the Externalizable interface
  public PolyLine() {
    this(0f, 0f);

  // The constructor.
  public PolyLine(float x0, float y0) {
    setOrigin(x0, y0); // Record the starting point.
    numsegs = 0; // Note that we have no line segments, so far

  /** Set the origin of the PolyLine. Useful when moving it */
  public void setOrigin(float x0, float y0) {
    this.x0 = x0;
    this.y0 = y0;

  /** Add dx and dy to the origin */
  public void translate(float dx, float dy) {
    this.x0 += dx;
    this.y0 += dy;

   * Add a line segment to the PolyLine. Note that x and y are absolute
   * coordinates, even though the implementation stores them relative to x0, y0;
  public void addSegment(float x, float y) {
    // Allocate or reallocate the coords[] array when necessary
    if (coords == null)
      coords = new float[32];
    if (numsegs * 2 >= coords.length) {
      float[] newcoords = new float[coords.length * 2];
      System.arraycopy(coords, 0, newcoords, 0, coords.length);
      coords = newcoords;

    // Convert from absolute to relative coordinates
    x = x - x0;
    y = y - y0;

    // Store the data
    coords[numsegs * 2] = x;
    coords[numsegs * 2 + 1] = y;

    // Enlarge the bounding box, if necessary
    if (x > xmax)
      xmax = x;
    else if (x < xmin)
      xmin = x;
    if (y > ymax)
      ymax = y;
    else if (y < ymin)
      ymin = y;

  /*------------------ The Shape Interface --------------------- */

  // Return floating-point bounding box
  public Rectangle2D getBounds2D() {
    return new Rectangle2D.Float(x0 + xmin, y0 + ymin, xmax - xmin, ymax - ymin);

  // Return integer bounding box, rounded to outermost pixels.
  public Rectangle getBounds() {
    return new Rectangle((int) (x0 + xmin - 0.5f), // x0
        (int) (y0 + ymin - 0.5f), // y0
        (int) (xmax - xmin + 0.5f), // width
        (int) (ymax - ymin + 0.5f)); // height

  // PolyLine shapes are open curves, with no interior.
  // The Shape interface says that open curves should be implicitly closed
  // for the purposes of insideness testing. For our purposes, however,
  // we define PolyLine shapes to have no interior, and the contains()
  // methods always return false.
  public boolean contains(Point2D p) {
    return false;

  public boolean contains(Rectangle2D r) {
    return false;

  public boolean contains(double x, double y) {
    return false;

  public boolean contains(double x, double y, double w, double h) {
    return false;

  // The intersects methods simply test whether any of the line segments
  // within a polyline intersects the given rectangle. Strictly speaking,
  // the Shape interface requires us to also check whether the rectangle
  // is entirely contained within the shape as well. But the contains()
  // methods for this class alwasy return false.
  // We might improve the efficiency of this method by first checking for
  // intersection with the overall bounding box to rule out cases that
  // aren't even close.
  public boolean intersects(Rectangle2D r) {
    if (numsegs < 1)
      return false;
    float lastx = x0, lasty = y0;
    for (int i = 0; i < numsegs; i++) { // loop through the segments
      float x = coords[i * 2] + x0;
      float y = coords[i * 2 + 1] + y0;
      // See if this line segment intersects the rectangle
      if (r.intersectsLine(x, y, lastx, lasty))
        return true;
      // Otherwise move on to the next segment
      lastx = x;
      lasty = y;
    return false; // No line segment intersected the rectangle

  // This variant method is just defined in terms of the last.
  public boolean intersects(double x, double y, double w, double h) {
    return intersects(new Rectangle2D.Double(x, y, w, h));

  // This is the key to the Shape interface; it tells Java2D how to draw
  // the shape as a series of lines and curves. We use only lines
  public PathIterator getPathIterator(final AffineTransform transform) {
    return new PathIterator() {
      int curseg = -1; // current segment

      // Copy the current segment for thread-safety, so we don't
      // mess up of a segment is added while we're iterating
      int numsegs = PolyLine.this.numsegs;

      public boolean isDone() {
        return curseg >= numsegs;

      public void next() {

      // Get coordinates and type of current segment as floats
      public int currentSegment(float[] data) {
        int segtype;
        if (curseg == -1) { // First time we're called
          data[0] = x0; // Data is the origin point
          data[1] = y0;
          segtype = SEG_MOVETO; // Returned as a moveto segment
        } else { // Otherwise, the data is a segment endpoint
          data[0] = x0 + coords[curseg * 2];
          data[1] = y0 + coords[curseg * 2 + 1];
          segtype = SEG_LINETO; // Returned as a lineto segment
        // If a tranform was specified, transform point in place
        if (transform != null)
          transform.transform(data, 0, data, 0, 1);
        return segtype;

      // Same as last method, but use doubles
      public int currentSegment(double[] data) {
        int segtype;
        if (curseg == -1) {
          data[0] = x0;
          data[1] = y0;
          segtype = SEG_MOVETO;
        } else {
          data[0] = x0 + coords[curseg * 2];
          data[1] = y0 + coords[curseg * 2 + 1];
          segtype = SEG_LINETO;
        if (transform != null)
          transform.transform(data, 0, data, 0, 1);
        return segtype;

      // This only matters for closed shapes
      public int getWindingRule() {
        return WIND_NON_ZERO;

  // PolyLines never contain curves, so we can ignore the flatness limit
  // and implement this method in terms of the one above.
  public PathIterator getPathIterator(AffineTransform at, double flatness) {
    return getPathIterator(at);

  /*------------------ Externalizable --------------------- */

   * The following two methods implement the Externalizable interface. We use
   * Externalizable instead of Seralizable so we have full control over the data
   * format, and only write out the defined coordinates
  public void writeExternal( out) throws {
    for (int i = 0; i < numsegs * 2; i++)

  public void readExternal( in) throws,
      ClassNotFoundException {
    this.x0 = in.readFloat();
    this.y0 = in.readFloat();
    this.numsegs = in.readInt();
    this.coords = new float[numsegs * 2];
    for (int i = 0; i < numsegs * 2; i++)
      coords[i] = in.readFloat();

  /*------------------ Cloneable --------------------- */

   * Override the Object.clone() method so that the array gets cloned, too.
  public Object clone() {
    try {
      PolyLine copy = (PolyLine) super.clone();
      if (coords != null)
        copy.coords = (float[]) this.coords.clone();
      return copy;
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(); // This should never happen

 * Copyright (c) 2004 David Flanagan. All rights reserved. This code is from the
 * book Java Examples in a Nutshell, 3nd Edition. It is provided AS-IS, WITHOUT
 * ANY WARRANTY either expressed or implied. You may study, use, and modify it
 * for any non-commercial purpose, including teaching and use in open-source
 * projects. You may distribute it non-commercially as long as you retain this
 * notice. For a commercial use license, or to purchase the book, please visit

 * This class implements the Transferable interface for PolyLine objects. It
 * also defines a DataFlavor used to describe this data type.
class TransferablePolyLine implements Transferable {
  public static DataFlavor FLAVOR = new DataFlavor(PolyLine.class, "PolyLine");

  static DataFlavor[] FLAVORS = new DataFlavor[] { FLAVOR };

  PolyLine line; // This is the PolyLine we wrap.

  public TransferablePolyLine(PolyLine line) {
    this.line = line;

  /** Return the supported flavor */
  public DataFlavor[] getTransferDataFlavors() {
    return FLAVORS;

  /** Check for the one flavor we support */
  public boolean isDataFlavorSupported(DataFlavor f) {
    return f.equals(FLAVOR);

  /** Return the wrapped PolyLine, if the flavor is right */
  public Object getTransferData(DataFlavor f) throws UnsupportedFlavorException {
    if (!f.equals(FLAVOR))
      throw new UnsupportedFlavorException(f);
    return line;

14.112.Drag Drop
14.112.1.Basic drag and drop
14.112.2.Dragging Text from a JLabelDragging Text from a JLabel
14.112.3.Drag-and-Drop Support for ImagesDrag-and-Drop Support for Images
14.112.4.Drag and drop icons: use an icon property.
14.112.5.implements DragGestureListener, Transferable
14.112.6.Dragging and dropping text between a text area, a list, and a tableDragging and dropping text between a text area, a list, and a table
14.112.7.Drag and drop between JTextArea and JTextField
14.112.8.Transfer both Text and Color between JTextField and JTextArea
14.112.9.Drag and drop between JList and JTextField
14.112.13.Set tree DropMode to DropMode.USE_SELECTION
14.112.14.Set tree drag mode to DropMode.ON
14.112.15.Set tree drag mode to DropMode.INSERT
14.112.16.Set tree drag mode to DropMode.ON_OR_INSERT
14.112.17.Choose Drop Action
14.112.18.Various drop actions
14.112.19.JTable drag and drop
14.112.20.Create a drag source a drop target and a transferable object.
14.112.21.Making a Component Draggable
14.112.22.Detect a drag initiating gesture in your application
14.112.23.Illustrates cut, copy, paste and drag and drop using three instances of JList
14.112.24.Location sensitive drag and drop
14.112.25.Demonstration of the top-level TransferHandler support on JFrame
14.112.26.Drag-and-Drop customization: drag the foreground color from the first label and drop it as the background color into the second one
14.112.27.Demonstrates how to add copy and drag support to a Swing component with TransferHandler
14.112.28.ScribblePane allows individual PolyLine lines to be selected, cut, copied, pasted, dragged, and dropped
14.112.29.Built-in drag and drop support: utilize a TransferHandler class
14.112.30.DND Drag and drop List